Skip to content

Instantly share code, notes, and snippets.

@rafegoldberg
Last active February 9, 2024 18:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rafegoldberg/39478cf025e7c353fe7de630dcd33e6c to your computer and use it in GitHub Desktop.
Save rafegoldberg/39478cf025e7c353fe7de630dcd33e6c to your computer and use it in GitHub Desktop.
find projects using hashed classes in their Custom CSS

List Hashed Selectors

  1. add a log to the defaultGetLocalIdent() method in the installed css-loader package here1
  2. open the React app's webpack config; comment out all the exports except for the Hub/Web build
  3. run build -w @readme/react | tee packages/cron/src/hashed-classes.txt > /dev/null2

Query Matching Projects

  1. paste this script in to packages/cron/src/hashed-class-search.js
  2. npm install parker
  3. run npx ts-node ./packages/cron/src/hashed-class-search.js2

Footnotes

  1. you'll probably need to find this method in the compiled code of the css-loader/dist/ directory

  2. it's important to run this from the root of our monorepo 2

/**
* Find and list every hashed class being used in our projects' Custom CSS
* @usage npx ts-node bin/custom-css-search.js
* @option --dataset=[classes|project]
* @note you can pipe the output directly in to a file using `tee`:
* `... | tee custom-css-usage.json >/dev/null`
*/
try {
require('dotenv').config(); // eslint-disable-line global-require
} catch (e) {} // eslint-disable-line no-empty
const fs = require('fs/promises');
const config = require('config');
const mongoose = require('@readme/models/mongoose');
let Parker;
try {
Parker = require('parker/lib/Parker');
} catch (e) {
console.log('\n🛑 Error\n You need to install Parker to run this script!\n\n $ npm install parker\n');
process.exit(0);
}
require('@readme/models');
const Project = mongoose.model('Project');
const sortObject = (obj, cb) =>
Object.fromEntries(
Object.entries(obj)
.sort((a, b) => cb(a, b))
.reverse(),
);
const dataset = process.argv
.find(e => /--dataset=(classes|projects)/i.test(e))
?.replaceAll('-', '')
.split('=')[1]
.toLowerCase();
async function main() {
await mongoose.connect(config.db);
let classes;
try {
classes = (await fs.readFile(`${__dirname}/search-classes.txt`, 'utf-8').then(c => c.split('\n'))).filter(Boolean);
if (!classes.length) throw new Error('No classes defined!');
} catch (e) {
console.log(
"\n🛑 Error:\n You need to add a list of class selectors to search for!\n To create one, run:\n\n $ echo 'MatchClass-zed\\nMatchClass-one' | tee bin/search-classes.txt > /dev/null\n",
);
process.exit(0);
}
const projects = await Project.find({
is_active: true,
'appearance.stylesheet_hub2': {
$exists: true,
$ne: '',
},
});
const parker = new Parker([
{
id: 'classes',
type: 'identifier',
aggregate: 'list',
format: 'list',
measure(selector) {
if (selector.startsWith('.')) {
if (classes.includes(selector.substr(1))) return selector;
}
return false;
},
filter(value, index, self) {
return self.indexOf(value) === index;
},
},
]);
let matchStats = {};
let usageStats = {};
const promises = projects.map(async project => {
const results = parker.run(project.appearance.stylesheet_hub2);
if (!results.classes.length) return;
// eslint-disable-next-line consistent-return
return new Promise(resolve => {
results.classes.forEach(c => {
matchStats[c] = matchStats[c] + 1 || 1;
});
usageStats[project.subdomain] = {
name: project.name || project.subdomain,
plan: project.planOverride || project.plan,
count: results.classes.length,
classes: results.classes.sort(),
link: project.childrenProjects.length
? `https://dash.readme.com/group/${project.subdomain}/stylesheet`
: `http://${project.subdomain}.readme.io/dash?to=appearance-stylesheet`,
};
resolve();
});
});
await Promise.all(promises);
console.log();
if (dataset === 'classes') {
matchStats = sortObject(matchStats, ([, a], [, b]) => a - b);
console.log(JSON.stringify(matchStats, null, 2));
} else if (dataset === 'projects') {
usageStats = sortObject(usageStats, ([, { count: a }], [, { count: b }]) => a - b);
console.log(JSON.stringify(usageStats, null, 2));
} else {
console.log('🐷 Matched Classes:', Object.keys(matchStats).length);
console.log(' Matched Projects:', Object.keys(usageStats).length);
if (typeof dataset === 'undefined')
console.log('\n (Pass --dataset=[projects|classes] to get more detailed information.)');
}
console.log();
process.exit(0);
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment