npm i -D lighthouse
npm i -D puppeteer
const browser =
await puppeteer.launch({
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--headless'
]});
const lighthouseConfig = {
port: (
new URL(browser.wsEndpoint())
).port,
output: 'json',
logLevel: 'info',
};
const auditConfig = {
extends: 'lighthouse:default',
settings: {
onlyCategories: ['performance'],
onlyAudits: [
'first-meaningful-paint',
'speed-index-metric',
'estimated-input-latency',
'first-interactive',
'consistently-interactive',
]}};
const {
lhr: {
audits
}} = await lighthouse(
'http://werkspot.nl/',
lighthouseConfig,
auditConfig);
if (audits["speed-index"].score < 0.7)
process.exit(1);
process.exit(0);
# .gitlab-ci.yml
image: node:latest
stages:
- performance
lighthouse:
stage: performance
before_script:
- apt-get update
- apt-get -y install gconf-service …
- node -v
- npm i
script:
- npm run lighthouse
Server-Timing:
cache;
desc="Cache Read";
dur=23.2
Server-Timing: cache
Chrome v60-64
Server-Timing:
cache=23.2;
"Cache Read"
const express = require('express');
const app = express();
app.get('/', (req, res) => {
//…
res.send(200).end();
});
app.listen(3000);
res.set(
'Server-Timing',
'cache;
desc="Cache Read";
dur=23.2');
// node.js v0.7.6
process.hrtime();
// integer[]
// [ seconds, nanoseconds ]
// [ 8063, 904046009 ]
const from =
process.hrtime();
const duration =
process.hrtime(from);
// To milliseconds
const ms = parseInt(
duration[0] * 1e3 +
duration[1] * 1e-6,
10);
// node.js v10.7.0, Stage 3
const from =
process.hrtime.bigint();
const to =
process.hrtime.bigint();
const duration = to - from;
// To milliseconds
const ms =
duration / 1000000n;
npm i -S server-timing-header
const serverTiming = require('server-timing-header');
const express = require('express');
const app = express();
app.use(serverTiming);
app.get('/', (req, res) => {
//…
res.send(200).end();
});
app.listen(3000);
const serverTiming = require('server-timing-header');
// const express = require('express');
// const app = express();
app.use(serverTiming);
// app.get('/', (req, res) => {
// …
// res.send(200).end();
// });
// app.listen(3000);
req.serverTiming.from('db');
// fetching data from database
req.serverTiming.to('db');
req.serverTiming.add(
'cache', // name
'Cache Read', //description
23.2 // value
);
req.serverTiming.add(
'cache',
'Element not in cache',
);
// not safari
// ff need https
performance.getEntriesByType('navigation');
performance.getEntriesByType('resource');
Timing-Allow-Origin: *
const {
renderToNodeStream
} = require("react-dom/server");
const React = require("react");
const express = require('express');
const app = express();
app.get('/', (req, res) => {/*…*/});
app.listen(3000);
res.writeHead(200, {
'Content-Type': 'text/html',
'Transfer-Encoding': 'chunked'
});
res.write("<main id='content'>");
const stream = renderToNodeStream(
React.createElement(
'p', null, 'XXX'));
stream.pipe(res, { end: false });
stream.on('end', () => {
res.write("</main>").end();
});
Server-Timing?
res.writeHead(200, {
'Trailer': 'Server-Timing',
// …
});
stream.on('end', () => {
res.write("</main>")
res.addTrailers({
'Server-Timing':
'metric;desc="metric 2";dur=12'
});
res.end();
});
HTTP/1.1 200 OK
Link: <https://widget.com>;
rel=dns-prefetch
Link: <https://example.com>;
rel=preconnect
Link: <https://example.com/next-page.html>;
rel=prerender;
Link: <https://example.com/logo-hires.jpg>;
rel=prefetch; as=image;
<link rel="dns-prefetch"
href="//widget.com">
<link rel="preconnect"
href="//cdn.example.com">
<link rel="prerender"
href="//example.com/next-page.html">
<link rel="prefetch"
href="//example.com/logo-hires.jpg"
as="image">
<link
rel="preload"
href="/goodmojo.css"
as="style">
HTTP/1.1 200 OK
Link:
</badmojo.css>;
rel=preload;
as=style
res.set('Link', [
'</badmojo.css>; rel=preload;
as=style',
'</badmojo.js>; rel=preload;
as=script',
'</font.ttf>; rel=preload;
as=font']);
HTTP/1.1 103 Early Hints
Link:
</hintmojo.css>;
rel=preload;
as=style
HTTP/1.1 200 OK
npm i -S early-hints
app.use(earlyHints([{
path: '/hintmojo.css',
rel: 'preload',
as: 'style'
}]));
Accept-CH:
Save-Data,
Downlink,
Device-Memory,
DPR,
Viewport-Width,
Width
<meta
http-equiv="Accept-CH"
content="Save-Data
Downlink,
Device-Memory,
DPR,
Viewport-Width,
Width" />
'save-data': 'on',
'downlink': '7.95',
'device-memory': '8',
'dpr': '1',
'viewport-width': '1306',
'width': '327',
Accept-CH-Lifetime: 86400
Vary: Device-Memory
Performance
PerformanceObserver
performance.getEntriesByType(entryType)
performance.getEntriesByName(name)
performance.getEntries()
const observer
= new PerformanceObserver(this.handler);
observer.observe({
entryTypes
});
performance.now()
performance.timeOrigin
const start = performance.now();
const end = performance.now();
const duration = end - start;
performance.mark("startTask1");
// Получение данных
const url = '/api/v1/employees';
const resource = await fetch(url);
performance.mark("endTask1");
const entries = performance
.getEntriesByType("mark");
for (const entry of entries) {
console.table(entry.toJSON());
}
performance.measure(
'task1',
'startTask1',
'endTask1'
);
const entries = performance
.getEntriesByType("measure");
for (const entry of entries) {
console.table(entry.toJSON());
}
performance.clearMeasures('task1')
performance.measure('from start');
const entries = performance
.getEntriesByType("measure");
for (const entry of entries) {
console.table(entry.toJSON());
}
performance.timing
performance.navigation
const entries = performance
.getEntriesByType("resource");
for (const entry of entries) {
console.table(entry.toJSON());
}
const plannedEntries = ['longtask'];
const entryTypes =
plannedEntries.filter(entry => {
return PerformanceObserver
.supportedEntryTypes
.includes(entry);
});
if (entryTypes.length > 0) {
const observer =
new PerformanceObserver(report);
observer.observe({
entryTypes
});
}
const express = require('express');
const formidable = require('express-formidable');
const path = require('path');
const port = 3333;
const app = express();
app.use(formidable({
encoding: 'utf-8',
uploadDir: path.join(
__dirname,
'uploads'),
multiples: true,
keepExtensions:true,
}));
app.use('/reports',
function (req, res) {
res.status(204).end();
console.log(req.fields);
});
app.use('/',
function (req, res) {
res.send(`/…/`)
.status(200)
.end();
});
app.listen(port);
let data = new FormData();
let items = {
some: 'data',
more: ['words', 'aside'],
};
for ( var key in items ) {
data.append(
key,
items[key]
);
}
navigator.sendBeacon('/reports', data);
{
some: 'data',
more: 'words,aside'
}
Антон Немцев
http://silentimp.info/
@silentimp
thesilentimp@gmail.com
skype: ravencry