203 lines
5.7 KiB
JavaScript
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);
|