Alfredo Lopez PRO
¯\_(ツ)_/¯
Empowering Performance Culture
,
Hearst is a media company with more than 360 businesses.
Its major interests include magazines, TV networks, newspapers and more.
Elle Logo
const perfume = new Perfume({
firstPaint: true,
firstContentfulPaint: true,
firstInputDelay: true,
googleAnalytics: {
enable: true,
timingVar: "userId"
}
})
// Perfume.js: First Paint 1482.00 ms
// Perfume.js: First Contentful Paint 2029.00 ms
// Perfume.js: First Input Delay 3.20 ms
Graph
Graph
200 KB
200 KB
// feed container
const feed = document.querySelector('.feed');
// get the closest element to the middle of the feed
const middleish = feed.children[Math.floor(feed.childElementCount / 2 )];
// create an IntersectionObserver
const io = new IntersectionObserver((entries) => {
entries.forEach(async (entry) => {
if (!entry.isIntersecting) return;
io.disconnect();
// import the module
const infiniteLoad = await import('app/modules/infinite-load')
infiniteLoad.setup(feed);
})
})
// observe the middle element
io.observe(middleish);
<a class="nav-search-button" href="#searchoverlay" title="Search">
<span class="icon icon-search"></span>
</a>
<section id=#searchoverlay">
<!-- modal content !-->
</section>
#searchoverlay {
position: fixed;
width: 100vw;
height: 100vh;
transition: opacity 0.3s ease-in;
z-index: -1;
}
#searchoverlay:target {
z-index: 0;
opacity: 1;
}
<details class="search">
<summary>Search</summary>
<section>
THIS IS A MODAL
</section>
</details>
details summary::-webkit-details-marker {
display:none;
}
details summary {
cursor: pointer;
outline: none !important;
display: inline-block;
/* etc */
}
details[open] > summary::before {
position: fixed;
cursor: default;
content: " ";
z-index: 99;
background: rgba(27,31,35,0.5);
}
details > section {
left: 50%;
margin: 10vh auto;
max-height: 80vh;
max-width: 90vw;
position: fixed;
top: 0;
transform: translateX(-50%);
}
// main.js
import * as Comlink from "comlink";
const worker = new Worker("worker.js");
// `app` lives in the worker
const app = await Comlink.wrap(worker);
const result = await app.doSomething();
console.log(result);
// worker.js
import * as Comlink from "comlink";
const app = {
doSomething() {
// perform operations
return result;
}
}
Comlink.expose(app);
<head>
<!-- worker-dom library -->
<script src="index.js" defer></script>
</head>
<body>
<section src="app.js" class="app-script"><p>Hello World!</p><input/></section>
<script defer>
document.addEventListener('DOMContentLoaded', function() {
// MainThread is defined by index.js, worker.js is also part of the library
MainThread.upgradeElement(document.getElementsByClassName('app-script')[0], './worker.js');
}, false);
</script>
</body>
// app.js
const p = document.createElement('p');
const text = document.createTextNode('Hello World!');
const input = document.createElement('input');
p.appendChild(text);
document.body.appendChild(p);
document.body.appendChild(input);
function toggle() {
p.style.color = p.style.color === "green" ? "red" : "green";
}
input.addEventListener('input', event => {
if (event.currentTarget.value === 'change') {
toggle();
}
}, false);
Loads page with ?third-parties
Tag manager loads all scripts
Collect all third-party files and store them
In-memory DB
Store the links
to the URL
On content invalidation,
or at a scheduled time
trigger the function
// module-a.js
requestAnimationFrame(() => {
const { right, bottom } = el.getBoundingClientRect();
});
// module-b.js
requestAnimationFrame(() => {
el.classList.add('change-size'); 😱
});
const io = new IntersectionObserver((entries) => {
entries.forEach(entry => {
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
});
});
// observe the element
io.observe(el);
const getBoundingClientRect = el =>
new Promise((resolve) => {
const io = new IntersectionObserver(([entry]) => {
<mark>resolve(entry.boundingClientRect);</mark>
io.disconnect();
});
io.observe(el);
});
const touchDevice = 'ontouchstart' in window;
const addListener = (target, type, listener) => {
// switch click to touchstart on touch devices
if (type === 'click' && touchDevice) {
type = 'touchstart';
}
target.addEventListener(type, listener);
}
This is where Performance mostly happens.
This is where we think
it should happen.
This is where it should happen.
Will be open sourced soon
Collect all open PRs daily
with labels E.g
"Ready to Merge"
Open PR
and assign users
from a config value
{
"baseUrl": "https://example.com",
"ci": "[ci]",
"type": "[type]",
"settings": {
"categories": {
"performance": 70,
"accessibility": 70,
"best-practices": 70,
"pwa": 70
},
"budgets": [
{
"resourceSizes": [
{
"resourceType": "script",
"budget": 300
}
],
"resourceCounts": [
{
"resourceType": "third-party",
"budget": 50
},
]
}
]
},
"routes": [
"/",
{ "url": "/articles", "settings": {...} }
]
}
{
"baseUrl": "https://example.com",
"ci": "[ci]",
"type": "[type]",
"settings": {
"categories": {
"pwa": {
"target": 90,
"threshold": 40,
"warning": 10
}
}
},
"sharedSettings": {
"galleries": {
"extends": true,
"categories": {
"pwa": {
"threshold": 20
}
},
"lighthouse": {
"options": {
"emulatedFormFactor": "desktop",
"extraHeaders": {
"X-CUSTOM-HEADER": "gallery-header"
}
}
}
}
},
"routes": [
"/article/1/",
{
"url": "gallery/1",
"settings": "galleries"
},
{
"url": "gallery/2",
"settings": {
"extends": "galleries",
"categories": {
"pwa": {
"target": 80
}
}
}
}
]
}
npm i -D lightkeeperbot
lightkeeperbot [optional-url] --pr=123 --repo=owner/name --config-path=my-custom-config.js
language: node_js
node_js: lts/*
jobs:
include:
- stage: build
name: "Builds Pull Request"
install:
- npm ci
script:
# Creates the Pull Request image
- sleep 6
after_success:
# deploy/create the url
- npx lightkeeperbot https://lightkeeper-test.lopez.now.sh/
- echo 'continue without waiting for response'
- stage: ui-tests
name: "Unit Tests"
script:
- sleep 20
- echo 'ran unit tests'
- stage: integration-tests
name: "Integration Tests"
script:
- sleep 20
- echo 'ran integration tests'
By Alfredo Lopez
Making room for performance optimizations is hard. It means different things to different people and is considered a goal to achieve and maintain. But a performance culture is not about being slow or fast, but the ability to make tradeoffs and tackle competing priorities, with a clear path back.