pagespeed-quest
Version:
A framework for efficient web front-end speed improvement
175 lines • 16.8 kB
JavaScript
import Fs from 'fs';
import Fsp from 'fs/promises';
import Path from 'path';
import { compress, decompress } from './encoding.js';
import { convertEditableText, isText } from './formatting.js';
import { parseContentTypeHeader, requestContentFilePath, stringifyContentTypeHeader } from './http.js';
const InventoryDir = 'inventory';
const IndexFile = 'index.json';
export class InventoryRepository {
dirPath;
dependency;
constructor(dirPath, dependency) {
this.dirPath = dirPath || Path.join(process.cwd(), InventoryDir);
this.dependency = dependency || {};
}
async saveInventory(inventory) {
const inventoryJson = JSON.stringify(inventory, null, 2);
await Fsp.mkdir(this.dirPath, { recursive: true });
await Fsp.writeFile(Path.join(this.dirPath, IndexFile), inventoryJson);
}
async loadInventory() {
const inventoryJson = await Fsp.readFile(Path.join(this.dirPath, IndexFile), 'utf8');
const inventory = JSON.parse(inventoryJson);
return inventory;
}
async saveTransactions(transactions) {
// To keep transactions order in Promise.all,
// store transactions and resources in a map.
const map = new Map();
await Fsp.mkdir(this.dirPath, { recursive: true });
const saveTransaction = async (transaction) => {
const resource = {
method: transaction.method,
url: transaction.url,
ttfbMs: transaction.ttfbMs,
statusCode: transaction.statusCode,
errorMessage: transaction.errorMessage,
rawHeaders: transaction.rawHeaders,
};
// Headers
if (transaction.rawHeaders) {
if (transaction.rawHeaders['content-type']) {
const { mime, charset } = parseContentTypeHeader(transaction.rawHeaders['content-type']);
if (mime)
resource.contentTypeMime = mime;
if (charset)
resource.contentTypeCharset = charset;
}
if (transaction.rawHeaders['content-encoding']) {
const contentEncoding = transaction.rawHeaders['content-encoding'];
if (contentEncoding)
resource.contentEncoding = contentEncoding;
}
}
// Mbps
if (transaction.durationMs && transaction.content) {
const contentBits = transaction.content.length * 8;
const seconds = transaction.durationMs / 1000;
const mega = 1024 * 1024;
resource.mbps = contentBits / seconds / mega;
}
// Content
if (transaction.content) {
const steps = {};
const contentFilePath = requestContentFilePath(resource.method, resource.url);
const fullPath = Path.join(this.dirPath, contentFilePath);
// Content-Encoding
steps.decoded = resource.contentEncoding
? await decompress(resource.contentEncoding, transaction.content)
: transaction.content;
// Try to make editable (utf8, beautify)
steps.editable = steps.decoded;
if (isText(resource.contentTypeMime)) {
try {
steps.editable = await convertEditableText(steps.decoded, resource.contentTypeMime, resource.contentTypeCharset);
resource.contentTypeCharset = 'utf-8';
}
catch (err) {
this.dependency.logger?.error({ err, resource }, `Formatting failed ${transaction.url}: ${err.message}`);
}
}
await Fsp.mkdir(Path.dirname(fullPath), { recursive: true });
await Fsp.writeFile(fullPath, steps.editable);
resource.contentFilePath = contentFilePath;
}
map.set(transaction, resource);
};
const tryToSaveTransaction = async (transaction) => {
try {
await saveTransaction(transaction);
}
catch (err) {
this.dependency.logger?.error({ err, method: transaction.method, url: transaction.url }, `Failed to save transaction ${transaction.url}: ${err.message}`);
}
};
await Promise.all(transactions.map(tryToSaveTransaction));
// Restore transactions order after Promise.all
const resources = transactions.reduce((resources, transaction) => {
const resource = map.get(transaction);
if (resource)
resources.push(resource);
return resources;
}, []);
return resources;
}
async loadTransactions(resources) {
const map = new Map();
const loadTransaction = async (resource) => {
const transaction = {
method: resource.method,
url: resource.url,
ttfbMs: resource.ttfbMs,
statusCode: resource.statusCode,
errorMessage: resource.errorMessage,
rawHeaders: { ...(resource.rawHeaders || {}) },
};
// content
let content;
if (resource.contentUtf8 !== undefined) {
content = Buffer.from(resource.contentUtf8);
}
else if (resource.contentBase64 !== undefined) {
content = Buffer.from(resource.contentBase64, 'base64');
}
else if (resource.contentFilePath) {
const fullPath = Path.join(this.dirPath, resource.contentFilePath);
if (Fs.existsSync(fullPath)) {
content = await Fsp.readFile(fullPath);
}
}
if (content) {
// encoding
if (resource.contentEncoding) {
transaction.content = await compress(resource.contentEncoding, content);
transaction.rawHeaders['content-encoding'] = resource.contentEncoding;
}
else {
transaction.content = content;
delete transaction.rawHeaders['content-encoding'];
}
// length
transaction.rawHeaders['content-length'] = `${transaction.content.length}`;
// duration
const bytesPerMs = resource.mbps ? (resource.mbps * 1024 * 1024) / 8 / 1000 : 0;
transaction.durationMs = bytesPerMs ? content.length / bytesPerMs : 0;
}
else {
transaction.rawHeaders['content-length'] = '0';
transaction.durationMs = 0;
}
// Content-Type
if (resource.contentTypeMime) {
transaction.rawHeaders['content-type'] = stringifyContentTypeHeader(resource.contentTypeMime, resource.contentTypeCharset);
}
map.set(resource, transaction);
};
const tryToLoadTransaction = async (resource) => {
try {
await loadTransaction(resource);
}
catch (err) {
this.dependency.logger?.error({ err, resource }, `Loading transaction failed ${resource.url}: ${err.message}`);
}
};
await Promise.all(resources.map(tryToLoadTransaction));
const transactions = resources.reduce((transactions, resource) => {
const transaction = map.get(resource);
if (transaction)
transactions.push(transaction);
return transactions;
}, []);
return transactions;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW52ZW50b3J5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ludmVudG9yeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsTUFBTSxJQUFJLENBQUE7QUFDbkIsT0FBTyxHQUFHLE1BQU0sYUFBYSxDQUFBO0FBQzdCLE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQTtBQUV2QixPQUFPLEVBQUUsUUFBUSxFQUF1QixVQUFVLEVBQUUsTUFBTSxlQUFlLENBQUE7QUFDekUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sRUFBRSxNQUFNLGlCQUFpQixDQUFBO0FBQzdELE9BQU8sRUFBZSxzQkFBc0IsRUFBRSxzQkFBc0IsRUFBRSwwQkFBMEIsRUFBRSxNQUFNLFdBQVcsQ0FBQTtBQUduSCxNQUFNLFlBQVksR0FBRyxXQUFXLENBQUE7QUFDaEMsTUFBTSxTQUFTLEdBQUcsWUFBWSxDQUFBO0FBc0M5QixNQUFNLE9BQU8sbUJBQW1CO0lBQzlCLE9BQU8sQ0FBUztJQUNoQixVQUFVLENBQStCO0lBRXpDLFlBQVksT0FBZ0IsRUFBRSxVQUEwQztRQUN0RSxJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsRUFBRSxZQUFZLENBQUMsQ0FBQTtRQUNoRSxJQUFJLENBQUMsVUFBVSxHQUFHLFVBQVUsSUFBSSxFQUFFLENBQUE7SUFDcEMsQ0FBQztJQUVELEtBQUssQ0FBQyxhQUFhLENBQUMsU0FBb0I7UUFDdEMsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxTQUFTLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFBO1FBQ3hELE1BQU0sR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUE7UUFDbEQsTUFBTSxHQUFHLENBQUMsU0FBUyxDQUFDLElB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