0.1.6Updated a month ago
import { walk } from "https://deno.land/std@0.224.0/fs/walk.ts";
import { join } from "jsr:@std/path";

const TAILWIND_CLASS_REGEX = /\b([\w\-:.\/\[#%\]\(\)\!]+)/g;

const classSet = new Set<string>();

async function extractClasses(filePath: string) {
  const content = await Deno.readTextFile(filePath);
  const matches = content.matchAll(TAILWIND_CLASS_REGEX);
  for (const match of matches) {
    classSet.add(match[1]);
  }
}

const ui_path = join(Deno.cwd(), '/ui');
const classes_path = join(Deno.cwd(), '/classes');

for await (const entry of walk(ui_path, { exts: [".tsx", ".ts", ".jsx", ".js", ".html"] })) {
  await extractClasses(entry.path);
}
for await (const entry of walk(classes_path, { exts: [".tsx", ".ts", ".jsx", ".js", ".html"] })) {
  if(!entry.path.includes('/ui/')) continue;

  await extractClasses(entry.path);
}

const rules = Array.from(classSet).sort().filter(rule => {
  if(rule.match(/^\d/)) return false;

  if(rule.startsWith('.') || rule.endsWith('.')) return false;
  if(rule.startsWith(':') || rule.endsWith(':')) return false;
  if(rule.startsWith('-') || rule.endsWith('-')) return false;

  if(rule.includes('!') && !rule.startsWith('!')) return false;

  if(rule.includes('::') && !rule.match(/^::(before|after|placeholder|marker)/)) return false;
  if(rule.includes('--')) return false;

  if(rule.match(/\[\s*\]/)) return false;
  if(rule.match(/\(\s*\)/)) return false;
  if(rule.match(/^[-:.\/#%\[\]()!]+$/)) return false;

  if((rule.match(/\[/g) || []).length !== (rule.match(/\]/g) || []).length) return false;
  if((rule.match(/\(/g) || []).length !== (rule.match(/\)/g) || []).length) return false;

  if((rule.match(/'/g) || []).length % 2 !== 0) return false;
  if((rule.match(/"/g) || []).length % 2 !== 0) return false;

  if(rule.match(/\w\.\w/) && !rule.match(/\[.*\w\.\w.*\]/)) return false;
  if(!rule.match(/^(?:\w+\:)?[a-zA-Z][a-zA-Z0-9-]*(-\[.*\]|-[a-zA-Z0-9]+)?$/)) return false;
  if(rule === rule.toUpperCase() && rule.match(/[A-Z]/)) return false;
  if(!rule.match(/[a-zA-Z]/)) return false;

  return true;
});

const required_classes = `// Generated by a build step in @viamedia/infinity-beyond
export const required_classes = () => <div className="${rules.join(' ')}" />;
`;

await Deno.writeTextFile("./dist/required-classes.tsx", required_classes);

console.log(`Required classes file generated with ${rules.length} classes`);