simonwep / simonwep/vite-plugin-optimize-css-modules

Class shaker

Open
#9 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

question
Dominant language
CSS
Stars
48
Forks
5
PR merge metrics
No merged PRs in 30d

Description

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

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the plugin's Vite integration and the JavaScript and CSS output stages. Review the proposed PurgeCSS custom extractor and the suggested { shake: true } option. Done means unused CSS Module classes are removed without removing referenced classes, with the behavior verified on a Vite build.

Written by the indexing model from the issue text.

Assessment

Tech stack
css, typescript, vite
Domain
build-system, frontend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.