Skip to content

Class shaker #9

@benallfree

Description

@benallfree

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`)
})

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions