ShowerLoop-cc/docker/showerloop/public/analyze-css.js

203 lines
5.7 KiB
JavaScript

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);