import puppeteer from 'puppeteer'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Configuration const siteUrl = 'http://localhost:1313'; // Your local Hugo server const pages = [ '/', '/about/', '/how-it-works/', '/posts/blog1/' // Add more pages to test ]; const outputFile = 'css-analysis.json'; // CSS files to analyze const cssFiles = [ '/css/vendor/material.indigo-pink.min.css', '/css/vendor/fontawesome.min.css', '/css/vendor/video-js.min.css', '/css/app.min.css', '/css/custom.css' ]; async function analyzePage(page, url) { await page.goto(url, { waitUntil: 'networkidle2' }); // Get all used CSS selectors const usedSelectors = await page.evaluate((cssFiles) => { const used = {}; cssFiles.forEach(file => { used[file] = []; }); // Function to get all CSS rules const getAllCssRules = (stylesheet) => { if (!stylesheet.href || !cssFiles.some(file => stylesheet.href.includes(file))) { return []; } const file = cssFiles.find(file => stylesheet.href.includes(file)); const rules = []; try { // Extract rules from stylesheet for (let i = 0; i < stylesheet.cssRules.length; i++) { const rule = stylesheet.cssRules[i]; if (rule.selectorText) { rules.push({ selector: rule.selectorText, file: file }); } else if (rule.cssRules) { // Handle media queries for (let j = 0; j < rule.cssRules.length; j++) { const nestedRule = rule.cssRules[j]; if (nestedRule.selectorText) { rules.push({ selector: nestedRule.selectorText, file: file }); } } } } } catch (e) { console.error('Error accessing stylesheet:', e); } return rules; }; // Get all stylesheets and their rules const allRules = []; Array.from(document.styleSheets).forEach(stylesheet => { allRules.push(...getAllCssRules(stylesheet)); }); // Test each selector allRules.forEach(({selector, file}) => { try { if (document.querySelector(selector) !== null) { used[file].push(selector); } } catch (e) { // Invalid selector, skip } }); return used; }, cssFiles); return usedSelectors; } async function run() { const browser = await puppeteer.launch(); const page = await browser.newPage(); console.log('Starting CSS analysis across pages...'); let results = {}; // Initialize results object cssFiles.forEach(file => { results[file] = { usedSelectors: new Set(), allSelectors: new Set() }; }); // Analyze each page for (const url of pages) { console.log(`Analyzing ${siteUrl}${url}...`); const pageResults = await analyzePage(page, `${siteUrl}${url}`); // Merge results for (const file in pageResults) { pageResults[file].forEach(selector => { results[file].usedSelectors.add(selector); }); } } // Get all selectors for (const file of cssFiles) { console.log(`Getting all selectors from ${file}...`); await page.goto(`${siteUrl}${file}`, { waitUntil: 'networkidle2' }); const allSelectors = await page.evaluate(() => { const text = document.querySelector('body')?.innerText || ''; // Very simplistic parsing - won't work for all cases // For production, use a proper CSS parser const selectors = []; const rules = text.split('}'); rules.forEach(rule => { const selectorPart = rule.split('{')[0]; if (selectorPart) { selectorPart.split(',').forEach(s => { s = s.trim(); if (s.length > 0) { selectors.push(s); } }); } }); return selectors; }); allSelectors.forEach(selector => { results[file].allSelectors.add(selector); }); } // Convert Sets to Arrays for JSON serialization const finalResults = {}; for (const file in results) { finalResults[file] = { used: Array.from(results[file].usedSelectors), all: Array.from(results[file].allSelectors), usage: { total: results[file].allSelectors.size, used: results[file].usedSelectors.size, unused: results[file].allSelectors.size - results[file].usedSelectors.size, percentage: (results[file].usedSelectors.size / results[file].allSelectors.size * 100).toFixed(2) } }; } // Generate summary const summary = { totalSelectorsUsed: Object.values(finalResults).reduce((sum, file) => sum + file.used.length, 0), totalSelectors: Object.values(finalResults).reduce((sum, file) => sum + file.all.length, 0), files: {} }; for (const file in finalResults) { summary.files[file] = finalResults[file].usage; } finalResults.summary = summary; // Save results fs.writeFileSync(outputFile, JSON.stringify(finalResults, null, 2)); console.log(`Analysis complete! Results saved to ${outputFile}`); console.log('Summary:'); console.log(`Total selectors: ${summary.totalSelectors}`); console.log(`Total used: ${summary.totalSelectorsUsed} (${(summary.totalSelectorsUsed / summary.totalSelectors * 100).toFixed(2)}%)`); for (const file in summary.files) { console.log(`${file}: ${summary.files[file].used}/${summary.files[file].total} (${summary.files[file].percentage}%)`); } await browser.close(); } run().catch(console.error);