UNPKG

pagespeed-quest

Version:

A framework for efficient web front-end speed improvement

189 lines 18.4 kB
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'; const InventoryFile = 'inventory.json'; // Rust proxy uses this name 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 }); // Save as both index.json (legacy) and inventory.json (Rust proxy) await Fsp.writeFile(Path.join(this.dirPath, IndexFile), inventoryJson); await Fsp.writeFile(Path.join(this.dirPath, InventoryFile), inventoryJson); } async loadInventory() { // Try inventory.json first (Rust proxy), fallback to index.json (legacy) let inventoryPath = Path.join(this.dirPath, InventoryFile); try { const inventoryJson = await Fsp.readFile(inventoryPath, 'utf8'); const inventory = JSON.parse(inventoryJson); return inventory; } catch { // Fallback to index.json inventoryPath = Path.join(this.dirPath, IndexFile); const inventoryJson = await Fsp.readFile(inventoryPath, '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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW52ZW50b3J5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ludmVudG9yeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsTUFBTSxJQUFJLENBQUE7QUFDbkIsT0FBTyxHQUFHLE1BQU0sYUFBYSxDQUFBO0FBQzdCLE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQTtBQUV2QixPQUFPLEVBQUUsUUFBUSxFQUF1QixVQUFVLEVBQUUsTUFBTSxlQUFlLENBQUE7QUFDekUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sRUFBRSxNQUFNLGlCQUFpQixDQUFBO0FBQzdELE9BQU8sRUFBZSxzQkFBc0IsRUFBRSxzQkFBc0IsRUFBRSwwQkFBMEIsRUFBRSxNQUFNLFdBQVcsQ0FBQTtBQUduSCxNQUFNLFlBQVksR0FBRyxXQUFXLENBQUE7QUFDaEMsTUFBTSxTQUFTLEdBQUcsWUFBWSxDQUFBO0FBQzlCLE1BQU0sYUFBYSxHQUFHLGdCQUFnQixDQUFBLENBQUMsNEJBQTRCO0FBc0NuRSxNQUFNLE9BQU8sbUJBQW1CO0lBQzlCLE9BQU8sQ0FBUztJQUNoQixVQUFVLENBQStCO0lBRXpDLFlBQVksT0FBZ0IsRUFBRSxVQUEwQztRQUN0RSxJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsRUFBRSxZQUFZLENBQUMsQ0FBQTtRQUNoRSxJQUFJLENBQUMsVUFBVSxHQUFHLFVBQVUsSUFBSSxFQUFFLENBQUE7SUFDcEMsQ0FBQztJQUVELEtBQUssQ0FBQyxhQUFhLENBQUMsU0FBb0I7UUFDdEMsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxTQUFTLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFBO1FBQ3hELE1BQU0sR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksRUFB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