vite-plugin-optimize-css-modules
vite-plugin-optimize-css-modules copied to clipboard
Class shaker
One thing that surprised me about CSS Modules in Vite is that it doesn't automatically shake unreferenced classes.
.unused {
font-size: 42px;
}
.used {
font-size: 27px;
}
import { used } from './foo.module.css'
...
The .unused class will appear in the output.
I noticed that this plugin does not shake unused classes either, so I made a small postprocessor that does the job.
If this seems like a good idea, I wonder if it could be incorporated into this plugin somehow. Basically it scans through the JS files to gather possible class names (/"([A-Za-z_]{1,2})"/g) then uses PurgeCSS with a custom extractor to strip the unused classes.
I think this could be incorporated as a { shake: true } option for this plugin. Shaking would require hooking into the JS output from Vite, marking known class names as used, and then stripping the unused ones before outputting the final CSS.
import { readFileSync, writeFileSync } from 'fs'
import { ExtractorResultDetailed, PurgeCSS } from 'purgecss'
const tags = ['a', 'abbr', ...]
const purgeFromJs = (content: string): ExtractorResultDetailed => {
const regex = /"([A-Za-z_]{1,2})"/g
const matches: string[] = []
let match: RegExpExecArray | null
while ((match = regex.exec(content)) !== null) {
matches.push(match[1])
}
// console.log(`Possible classes (${matches.length}): ${matches.join(', ')}`)
return {
attributes: {
names: [],
values: [],
},
ids: [],
tags,
undetermined: [],
classes: matches,
}
}
const purgeCSSResult = await new PurgeCSS().purge({
content: ['dist/assets/*.js'],
css: ['dist/assets/*.css'],
extractors: [
{
extractor: purgeFromJs,
extensions: ['js'],
},
],
})
// process.exit(0)
purgeCSSResult.forEach((result) => {
const { css, file } = result
if (!file) return
const bytesBefore = Buffer.byteLength(readFileSync(file, 'utf-8'), 'utf-8')
const bytesAfter = Buffer.byteLength(css, 'utf-8')
console.log(`Writing optimized css: ${file}`)
writeFileSync(file, css)
console.log(`Reduced size from ${bytesBefore} to ${bytesAfter} bytes`)
})
Hey! Great solution you've come up with, I'm just not sure if this should be done at compile level - unused css does more harm than ending up at the client. I'm speaking of maintainability, component pollution and such. This would be something that should probably be done during linting or so, or what do you think? :)