@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.
- package/.esbuild.browser.js +11 -0
- package/.esbuild.esm.js +25 -0
- package/.esbuild.node.js +16 -0
- package/.gitpod.yml +8 -0
- package/CHANGELOG.md +22 -1
- package/README.md +29 -66
- package/dist/cjs/1byte.js +52 -0
- package/dist/cjs/1byte.js.map +7 -0
- package/dist/cjs/1byte.test.js +27 -0
- package/dist/cjs/1byte.test.js.map +7 -0
- package/dist/cjs/co2.js +121 -0
- package/dist/cjs/co2.js.map +7 -0
- package/dist/cjs/co2.test.js +258 -0
- package/dist/cjs/co2.test.js.map +7 -0
- package/dist/cjs/constants/file-size.js +27 -0
- package/dist/cjs/constants/file-size.js.map +7 -0
- package/dist/cjs/constants/index.js +27 -0
- package/dist/cjs/constants/index.js.map +7 -0
- package/dist/cjs/helpers/index.js +24 -0
- package/dist/cjs/helpers/index.js.map +7 -0
- package/dist/cjs/hosting-api.js +64 -0
- package/dist/cjs/hosting-api.js.map +7 -0
- package/dist/cjs/hosting-api.test.js +47 -0
- package/dist/cjs/hosting-api.test.js.map +7 -0
- package/dist/cjs/hosting-database.node.test.js +36 -0
- package/dist/cjs/hosting-database.node.test.js.map +7 -0
- package/dist/cjs/hosting-json.node.js +73 -0
- package/dist/cjs/hosting-json.node.js.map +7 -0
- package/dist/cjs/hosting-json.node.test.js +43 -0
- package/dist/cjs/hosting-json.node.test.js.map +7 -0
- package/dist/cjs/hosting-node.js +78 -0
- package/dist/cjs/hosting-node.js.map +7 -0
- package/dist/cjs/hosting.js +36 -0
- package/dist/cjs/hosting.js.map +7 -0
- package/dist/cjs/hosting.test.js +74 -0
- package/dist/cjs/hosting.test.js.map +7 -0
- package/dist/cjs/index-node.js +29 -0
- package/dist/cjs/index-node.js.map +7 -0
- package/dist/cjs/index.js +31 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/sustainable-web-design.js +134 -0
- package/dist/cjs/sustainable-web-design.js.map +7 -0
- package/dist/cjs/sustainable-web-design.test.js +79 -0
- package/dist/cjs/sustainable-web-design.test.js.map +7 -0
- package/dist/esm/1byte.js +32 -0
- package/dist/esm/1byte.test.js +11 -0
- package/dist/esm/co2.js +98 -0
- package/{src → dist/esm}/co2.test.js +47 -94
- package/dist/esm/constants/file-size.js +7 -0
- package/dist/esm/constants/index.js +4 -0
- package/dist/esm/helpers/index.js +4 -0
- package/dist/esm/hosting-api.js +41 -0
- package/dist/esm/hosting-api.test.js +31 -0
- package/{src/hosting-database.test.js → dist/esm/hosting-database.node.test.js} +7 -18
- package/{src/hosting-json.test.js → dist/esm/hosting-json.node.test.js} +4 -21
- package/dist/esm/hosting.js +13 -0
- package/{src → dist/esm}/hosting.test.js +10 -30
- package/dist/esm/index.js +8 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/sustainable-web-design.js +111 -0
- package/{src → dist/esm}/sustainable-web-design.test.js +19 -37
- package/dist/iife/index.js +2 -0
- package/dist/iife/index.js.map +7 -0
- package/fixup +19 -0
- package/package.json +38 -17
- package/public/index.html +58 -0
- package/public/index.js +2 -0
- package/src/1byte.js +2 -3
- package/src/co2.js +17 -18
- package/src/constants/file-size.js +2 -2
- package/src/constants/index.js +2 -2
- package/src/helpers/index.js +1 -3
- package/src/hosting-api.js +23 -43
- package/src/{hosting-json.js → hosting-json.node.js} +9 -11
- package/src/hosting-node.js +94 -0
- package/src/hosting.js +6 -28
- package/src/index-node.js +4 -0
- package/src/index.js +4 -6
- package/src/sustainable-web-design.js +37 -11
- package/.eslintrc.json +0 -21
- package/.github/workflows/unittests.yml +0 -26
- package/data/Lean-ICT-Materials-1byte-Model-2018.xlsx +0 -0
- package/data/Lean-ICT-Materials-Forecast-Model-2018.xlsx +0 -0
- package/data/fixtures/tgwf.har +0 -6107
- package/data/fixtures/url2green.test.db +0 -0
- package/data/fixtures/url2green.test.json +0 -1
- package/data/fixtures/url2green.test.json.gz +0 -0
- package/images/swd-energy-usage.png +0 -0
- package/src/1byte.test.js +0 -17
- package/src/green-byte.js +0 -26
- 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>
|
package/public/index.js
ADDED
|
@@ -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
package/src/co2.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
11
|
+
this.model = new OneByte();
|
|
12
12
|
|
|
13
13
|
if (options) {
|
|
14
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
140
|
+
export { CO2 };
|
|
141
|
+
export default CO2;
|
package/src/constants/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
import fileSize from "./file-size.js";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export { fileSize };
|
package/src/helpers/index.js
CHANGED
package/src/hosting-api.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const
|
|
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
|
|
17
|
-
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
52
|
+
export default {
|
|
73
53
|
check,
|
|
74
54
|
};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
53
|
-
return val.green;
|
|
54
|
-
});
|
|
54
|
+
const greenEntries = entries.filter(([key, val]) => val.green);
|
|
55
55
|
|
|
56
|
-
return greenEntries.map(
|
|
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
|
-
|
|
4
|
-
const
|
|
5
|
-
const hostingJSON = require("./hosting-json");
|
|
3
|
+
import debugFactory from "debug";
|
|
4
|
+
const log = debugFactory("tgwf:hosting");
|
|
6
5
|
|
|
7
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
12
|
+
export default {
|
|
32
13
|
check,
|
|
33
|
-
checkPage,
|
|
34
|
-
greenDomains: greenDomainsFromResults,
|
|
35
|
-
loadJSON: hostingJSON.loadJSON,
|
|
36
14
|
};
|
package/src/index.js
CHANGED
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
*
|
|
9
9
|
*
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
|
-
const
|
|
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.
|
|
33
|
-
const RETURNING_VISITOR_PERCENTAGE = 0.
|
|
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
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
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
|
|
Binary file
|
|
Binary file
|