pagespeed-quest
Version:
A framework for efficient web front-end speed improvement
230 lines • 23.7 kB
JavaScript
#!/usr/bin/env node
import Path from 'path';
import { Command } from 'commander';
import Watch from 'node-watch';
import { Dependency } from './dependency.js';
import { execLighthouse } from './lighthouse.js';
import { execLoadshow } from './loadshow.js';
import { execWebshotCapture, execWebshotCompare } from './webshot.js';
import { InventoryRepository, withPlaybackProxy, withRecordingProxy } from './index.js';
const dependency = new Dependency();
function collect(value, previous) {
return previous.concat([value]);
}
const main = new Command();
main.option('-i, --inventory <dir>', 'Inventory directory', './inventory');
function registerLighthouseCommands(main) {
const lighthouse = main.command('lighthouse');
lighthouse.description('Run Lighthouse (performance category) via a proxy');
lighthouse.option('-a, --artifacts <dir>', 'Artifacts directory', './artifacts');
lighthouse.option('-l, --laud', 'Show browser and open the report after completion', false);
lighthouse.option('-t, --timeout <ms>', 'Timeout milliseconds', '30000');
const recording = lighthouse.command('recording');
recording.description('Record contents by lighthouse');
recording.option('-d, --device <mobile|desktop>', 'Device type', 'mobile');
recording.option('-x, --exclude <pattern>', 'Regex patterns to exclude URLs from recording (repeatable)', collect, []);
recording.argument('<url>', 'Url to measure performance');
recording.action(async (url) => {
const inventoryRepository = new InventoryRepository(main.opts().inventory || './inventory');
const deviceType = recording.opts().device || 'mobile';
const artifactsDir = lighthouse.opts().artifacts || './artifacts';
const laud = !!lighthouse.opts().laud;
const timeout = Number(lighthouse.opts().timeout || '30000');
const excludePatterns = recording.opts().exclude || [];
await withRecordingProxy({
entryUrl: url,
deviceType,
inventoryRepository,
excludePatterns: excludePatterns.length > 0 ? excludePatterns : undefined,
}, dependency, async (proxy) => {
await execLighthouse({
url,
proxyPort: proxy.port,
deviceType,
view: laud,
artifactsDir,
headless: !laud,
timeout,
}, dependency);
dependency.logger?.info('Lighthouse completed. Saving inventory...');
});
});
const playback = lighthouse.command('playback');
playback.description('Playback contents for lighthouse');
playback.action(async () => {
const inventoryDir = main.opts().inventory || './inventory';
const inventoryRepository = new InventoryRepository(inventoryDir);
const artifactsDir = lighthouse.opts().artifacts || './artifacts';
const laud = !!lighthouse.opts().laud;
const timeout = Number(lighthouse.opts().timeout || '30000');
// Load inventory for traffic statistics
const inventory = await inventoryRepository.loadInventory();
await withPlaybackProxy({
inventoryRepository,
}, dependency, async (proxy) => {
await execLighthouse({
url: proxy.entryUrl,
proxyPort: proxy.port,
deviceType: proxy.deviceType,
view: laud,
artifactsDir,
headless: !laud,
timeout,
inventoryDir,
resources: inventory.resources,
}, dependency);
dependency.logger?.info('Lighthouse completed');
});
});
}
function registerLoadshowCommands(main) {
const loadshow = main.command('loadshow');
loadshow.description('Run loadshow via a proxy');
loadshow.option('-a, --artifacts <dir>', 'Artifacts directory', './artifacts');
loadshow.option('-c, --credit <string>', 'Credit string');
loadshow.option('-t, --timeout <ms>', 'Timeout milliseconds', '30000');
const recording = loadshow.command('recording');
recording.description('Record contents by loadshow');
recording.option('-d, --device <mobile|desktop>', 'Device type', 'mobile');
recording.option('-x, --exclude <pattern>', 'Regex patterns to exclude URLs from recording (repeatable)', collect, []);
recording.argument('<url>', 'Url to measure performance');
recording.action(async (url) => {
const inventoryRepository = new InventoryRepository(main.opts().inventory || './inventory');
const deviceType = recording.opts().device || 'mobile';
const artifactsDir = loadshow.opts().artifacts || './artifacts';
const credit = loadshow.opts().credit || '';
const timeout = Number(loadshow.opts().timeout || '30000');
const excludePatterns = recording.opts().exclude || [];
await withRecordingProxy({
entryUrl: url,
deviceType,
inventoryRepository,
excludePatterns: excludePatterns.length > 0 ? excludePatterns : undefined,
}, dependency, async (proxy) => {
await execLoadshow({ url, proxyPort: proxy.port, deviceType, artifactsDir, credit, timeout }, dependency);
dependency.logger?.info('Loadshow completed. Saving inventory...');
});
});
const playback = loadshow.command('playback');
playback.description('Playback contents for loadshow');
playback.action(async () => {
const inventoryRepository = new InventoryRepository(main.opts().inventory || './inventory');
const artifactsDir = loadshow.opts().artifacts || './artifacts';
const credit = loadshow.opts().credit || '';
const timeout = Number(loadshow.opts().timeout || '30000');
await withPlaybackProxy({
inventoryRepository,
}, dependency, async (proxy) => {
await execLoadshow({
url: proxy.entryUrl,
proxyPort: proxy.port,
deviceType: proxy.deviceType,
artifactsDir,
credit,
timeout,
}, dependency);
dependency.logger?.info('Loadshow completed');
});
});
}
function registerProxyCommands(main) {
const proxy = main.command('proxy');
proxy.option('-p, --port <number>', 'Proxy port', '8080');
proxy.option('-r, --record <url>', 'Recording URL to start the proxy as recording mode', '');
proxy.option('-x, --exclude <pattern>', 'Regex patterns to exclude URLs from recording (repeatable)', collect, []);
proxy.action(async () => {
const inventoryRepository = new InventoryRepository(main.opts().inventory || './inventory');
const port = Number(proxy.opts().port || '8080');
if (proxy.opts().record) {
const url = proxy.opts().record;
if (!url) {
throw new Error('Recording URL must be specified with --record option.');
}
const excludePatterns = proxy.opts().exclude || [];
// Recordingモード
await withRecordingProxy({
inventoryRepository,
port,
entryUrl: url,
excludePatterns: excludePatterns.length > 0 ? excludePatterns : undefined,
}, dependency, async () => {
dependency.logger?.info(`Recording proxy started on port ${port}. Press Ctrl+C to stop.`);
// Wait for Ctrl+C signal
return new Promise((resolve) => {
process.on('SIGINT', () => {
dependency.logger?.info('Saving the inventory...');
resolve();
});
});
});
}
else {
// Playbackモード
// eslint-disable-next-line no-constant-condition
while (true) {
await withPlaybackProxy({ inventoryRepository, port }, dependency, async () => {
const watcher = Watch(inventoryRepository.dirPath, { recursive: true });
return new Promise((ok) => {
watcher.on('change', () => {
watcher.close();
dependency.logger?.info('Inventory changed. Restarting proxy...');
ok();
});
});
});
}
}
});
}
function registerCaptureCommands(main) {
const visualTest = main.command('capture');
visualTest.description('Capture a screenshot via playback proxy and optionally compare with a baseline');
visualTest.option('-a, --artifacts <dir>', 'Artifacts directory', './artifacts');
visualTest.option('-t, --timeout <ms>', 'Timeout milliseconds', '30000');
visualTest.option('--compare <file>', 'Baseline PNG file to compare against');
visualTest.option('--baseline-label <label>', 'Label for the baseline image', '前回');
visualTest.option('--current-label <label>', 'Label for the current image', '今回');
visualTest.action(async () => {
const inventoryRepository = new InventoryRepository(main.opts().inventory || './inventory');
const artifactsDir = visualTest.opts().artifacts || './artifacts';
const timeout = Number(visualTest.opts().timeout || '30000');
const compareFile = visualTest.opts().compare;
const baselineLabel = visualTest.opts().baselineLabel || '前回';
const currentLabel = visualTest.opts().currentLabel || '今回';
await withPlaybackProxy({
inventoryRepository,
fullThrottle: true,
}, dependency, async (proxy) => {
const outputPath = Path.join(artifactsDir, 'capture.png');
await dependency.mkdirp(artifactsDir);
await execWebshotCapture({
url: proxy.entryUrl,
proxyPort: proxy.port,
preset: proxy.deviceType === 'desktop' ? 'desktop' : 'mobile',
output: outputPath,
timeout,
}, dependency);
dependency.logger?.info(`Screenshot saved to ${outputPath}`);
if (compareFile) {
const diffOutput = Path.join(artifactsDir, 'capture-diff.png');
const digestTxt = Path.join(artifactsDir, 'capture-diff.txt');
await execWebshotCompare({
baseline: compareFile,
current: outputPath,
output: diffOutput,
digestTxt,
baselineLabel,
currentLabel,
}, dependency);
dependency.logger?.info(`Diff image saved to ${diffOutput}`);
dependency.logger?.info(`Diff digest saved to ${digestTxt}`);
}
});
});
}
registerLighthouseCommands(main);
registerLoadshowCommands(main);
registerProxyCommands(main);
registerCaptureCommands(main);
main.parse(process.argv);
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tbWFuZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9jb21tYW5kLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFFQSxPQUFPLElBQUksTUFBTSxNQUFNLENBQUE7QUFFdkIsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLFdBQVcsQ0FBQTtBQUNuQyxPQUFPLEtBQUssTUFBTSxZQUFZLENBQUE7QUFFOUIsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGlCQUFpQixDQUFBO0FBQzVDLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQTtBQUNoRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sZUFBZSxDQUFBO0FBRTVDLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGNBQWMsQ0FBQTtBQUVyRSxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsaUJBQWlCLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFFdkYsTUFBTSxVQUFVLEdBQUcsSUFBSSxVQUFVLEVBQUUsQ0FBQTtBQUVuQyxTQUFTLE9BQU8sQ0FBQyxLQUFhLEVBQUUsUUFBa0I7SUFDaEQsT0FBTyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQTtBQUNqQyxDQUFDO0FBRUQsTUFBTSxJQUFJLEdBQUcsSUFBSSxPQUFPLEVBQUUsQ0FBQTtBQUMxQixJQUFJLENBQUMsTUFBTSxDQUFDLHVCQUF1QixFQUFFLHFCQUFxQixFQUFFLGFBQWEsQ0FBQyxDQUFBO0FBRTFFLFNBQVMsMEJBQTBCLENBQUMsSUFBYTtJQUMvQyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLFlBQVksQ0FBQyxDQUFBO0lBQzdDLFVBQVUsQ0FBQyxXQUFXLENBQUMsbURBQW1ELENBQUMsQ0FBQTtJQUMzRSxVQUFVLENBQUMsTUFBTSxDQUFDLHVCQUF1QixFQUFFLHFCQUFxQixFQUFFLGFBQWEsQ0FBQyxDQUFBO0lB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