Skip to content

Commit

Permalink
refactor: Purge plugin overhaul (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
AdrianGonz97 authored Apr 9, 2024
1 parent d66aea0 commit 0f75105
Show file tree
Hide file tree
Showing 18 changed files with 1,097 additions and 553 deletions.
5 changes: 5 additions & 0 deletions .changeset/fair-moons-visit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'vite-plugin-tailwind-purgecss': minor
---

breaking: Updated the default purging strategy to only target Tailwind specific classes
5 changes: 5 additions & 0 deletions .changeset/khaki-squids-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'vite-plugin-tailwind-purgecss': minor
---

feat: Bundle size differences are now printed during build
5 changes: 5 additions & 0 deletions .changeset/nervous-drinks-agree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'vite-plugin-tailwind-purgecss': minor
---

feat: Added `legacy` mode that brings back the old plugin behavior
5 changes: 5 additions & 0 deletions .changeset/plenty-dots-breathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'vite-plugin-tailwind-purgecss': minor
---

breaking: Updated plugin option types
5 changes: 5 additions & 0 deletions .changeset/seven-seas-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'vite-plugin-tailwind-purgecss': minor
---

breaking: Added `tailwindcss` (v3.3.0 or higher) as a peer-dependency
109 changes: 60 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,41 @@
[![license](https://img.shields.io/badge/license-MIT-%23bada55)](https://github.com/AdrianGonz97/vite-plugin-tailwind-purgecss/blob/main/LICENSE)

> [!IMPORTANT]
> This plugin will no longer be necessary after the release of [Tailwind v4](https://tailwindcss.com/blog/tailwindcss-v4-alpha) as they will introduce a new [Vite plugin](https://tailwindcss.com/blog/tailwindcss-v4-alpha#zero-configuration-content-detection) that will detect and generate the classes based on the module graph! As such, this plugin will only support Tailwind v3.
> As of `v0.3.0`, `vite-plugin-tailwind-purgecss` no longer purges **all** unused CSS. Instead, it takes a more conservative and focused approach, only purging unused **tailwindcss classes**. The previous purging strategy introduced too many bugs and reached far outside of its intended scope. If you wish to reenable the old behavior, see [legacy mode](/legacy-mode.md).
A simple vite plugin that **thoroughly** purges excess CSS using [PurgeCSS](https://purgecss.com/). This package should be used in combination with [Tailwind](https://tailwindcss.com/) and a Tailwind UI component library such as [Skeleton](https://skeleton.dev) or [Flowbite](https://flowbite.com/).
A simple vite plugin that purges excess Tailwind CSS classes. This plugin should be used in combination with [TailwindCSS](https://tailwindcss.com/) and a Tailwind UI component library, such as [Skeleton](https://skeleton.dev), [Flowbite](https://flowbite.com/) or even [shadcn-ui](https://ui.shadcn.com/) (and it's [many ports](https://shadcn-svelte.com/)).

## Motivation

Tailwind UI component libraries are fantastic and are a joy to work with, but they come with an important caveat. The downside to them is that all of the Tailwind classes that are used in their provided components are _always_ generated, even if you don't import and use any of their components. This leads to a larger than necessary CSS bundle.
> [!NOTE]
> This plugin will no longer be necessary after the release of [Tailwind v4](https://tailwindcss.com/blog/tailwindcss-v4-alpha) as they are introducing a new [Vite plugin](https://tailwindcss.com/blog/tailwindcss-v4-alpha#zero-configuration-content-detection) that will detect and generate the tailwind classes based on the module graph! As such, this plugin will only support Tailwind v3.
This is a limitation of how Tailwind works with it's `content` field in its config. Tailwind searches through all of the files that are specified in `content`, uses a regex to find any possible selectors, and generates their respective classes. There's no treeshaking and no purging involved.
Tailwind UI component libraries are fantastic and a joy to work with, but they come with an important caveat. The downside to them is that all of the Tailwind classes that are used in **their provided components** are _always_ generated, **even if you don't import and use any of them**. This leads to a larger than necessary CSS bundle.

## Why not use `postcss-purgecss` or `rollup-plugin-purgecss`?

PurgeCSS provides a suite of plugins that do a well enough job for _most_ projects. However, plugins such as [postcss-purgecss](https://github.com/FullHuman/purgecss/tree/main/packages/postcss-purgecss) and [rollup-plugin-purgecss](https://github.com/FullHuman/purgecss/tree/main/packages/rollup-plugin-purgecss) take a rather naive approach to selector extraction. Just like how Tailwind generates its classes, they _only_ analyze the content and files that are passed to them through their `content` fields, which means that if you pass a UI library to it, none of the selectors that are **unused** in your project will be properly purged.
This is a limitation of how Tailwind works with the config's `content` field. Tailwind searches through all of the files that are specified in `content`, uses a regex to find any possible selectors, and generates their respective classes. There's no treeshaking and no purging involved.

## How it works

Ideally, we'd like to keep the selectors that are only being used in your project. We accomplish by analyzing the emitted JS chunks that are generated by Rollup, which only include the modules that are actually used in your project, and extracting out any of their possible selectors. From there, we can pass along the selectors to PurgeCSS for final processing.
Ideally, we only want to keep the tailwind classes that are being used in your project. We accomplish this by analyzing the files in the module graph and extracting out any of their possible selectors. From there, we pass along the selectors to PurgeCSS for final processing.

Using `vite-plugin-tailwind-purgecss` with [Skeleton](https://skeleton.dev), we're able to reduce the CSS bundle size of Skeleton's [Barebones Template](https://github.com/skeletonlabs/skeleton-template-bare) from:

```
92.50 kB │ gzip: 12.92 kB
105.62 kB │ gzip: 14.36 kB
```

down to:

```
27.29 kB │ gzip: 5.40 kB
16.33 kB │ gzip: 4.08 kB
```

## Usage

### Installation

```bash
npm i -D vite-plugin-tailwind-purgecss
npm add -D vite-plugin-tailwind-purgecss
```

### Add to Vite
Expand All @@ -50,50 +52,59 @@ const config: UserConfig = {
};
```

### Safelisting
...and you're all set!

If selectors that shouldn't be purged are being removed, simply add them to the `safelist`.
<details>
<summary><h2>Plugin Config Options</h2></summary>

```ts
// vite.config.ts
import { purgeCss } from 'vite-plugin-tailwind-purgecss';

const config: UserConfig = {
plugins: [
sveltekit(),
purgeCss({
safelist: {
// any selectors that begin with "hljs-" will not be purged
greedy: [/^hljs-/],
},
}),
],
export type PurgeOptions = {
/**
* Path to your tailwind config. This can normally be automatically detected if the config
* is located in the root of the project.
*
* Provide a path if your config resides anywhere outside of the root.
*/
tailwindConfigPath?: string;
/**
* Enables `legacy` mode. (not recommended)
*
* Legacy mode brings back the old plugin behavior (`v0.2.1` and below) where all unused CSS is purged,
* not just Tailwind classes. This mode is not recommended as it's too broad and can introduce
* unexpected bugs.
*
* **Use with caution!**
* @default false
*/
legacy?: boolean;
/**
* A subset of PurgeCSS options.
*
* `legacy` must be set to `true` to enable.
*/
purgecss?: PurgeCSSOptions;
/**
* A list of selectors that should be included in final CSS.
*
* **Note:** The safelist defined in your `tailwind.config.js` is already included.
*
* `legacy` must be set to `true` to enable.
*/
safelist?: ComplexSafelist;
/**
* Enables `debug` mode.
*
* Incurs a large performance cost, dramatically slowing down build times.
* @default false
*/
debug?: boolean;
};
```

Alternatively, if you'd like to safelist selectors directly in your stylesheets, you can do so by adding special comments:

```css
/*! purgecss start ignore */
</details>

h1 {
color: red;
}
## FAQ

h2 {
color: blue;
}

/*! purgecss end ignore */
```

You can also safelist selectors directly in the declaration block as well:

```css
h3 {
/*! purgecss ignore current */
color: pink;
}
```
### Why not use `postcss-purgecss` or `rollup-plugin-purgecss`?

For further configuration, you can learn more [here](https://purgecss.com/configuration.html).
PurgeCSS provides a suite of plugins that do a well enough job for _most_ projects. However, plugins such as [postcss-purgecss](https://github.com/FullHuman/purgecss/tree/main/packages/postcss-purgecss) and [rollup-plugin-purgecss](https://github.com/FullHuman/purgecss/tree/main/packages/rollup-plugin-purgecss) take a rather naive approach to selector extraction. Similar to how Tailwind generates its classes, they _only_ analyze the files that are passed to them through their `content` fields, which means that if you pass an entire UI library to it, none of the selectors that are **unused** in _your project_ will be properly purged.
67 changes: 67 additions & 0 deletions legacy-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# vite-plugin-tailwind-purgecss

## Legacy Mode

In previous versions (`v0.2.1` and below), `vite-plugin-tailwind-purgecss` would purge _all_ unused selectors. This proved to be problematic and was beyond the scope of the project. For backwards compatibility, this feature can be brought back by enabling _legacy mode_.

## Usage

Legacy mode can be enabled through the following plugin config option:

```ts
// vite.config.ts
import { purgeCss } from 'vite-plugin-tailwind-purgecss';

const config: UserConfig = {
plugins: [purgeCss({ legacy: true })],
};
```

### Safelisting

If selectors that shouldn't be purged are being removed, simply add them to the `safelist`.

```ts
// vite.config.ts
import { purgeCss } from 'vite-plugin-tailwind-purgecss';

const config: UserConfig = {
plugins: [
sveltekit(),
purgeCss({
safelist: {
// any selectors that begin with "hljs-" will not be purged
greedy: [/^hljs-/],
},
legacy: true,
}),
],
};
```

Alternatively, if you'd like to safelist selectors directly in your stylesheets, you can do so by adding special comments:

```css
/*! purgecss start ignore */

h1 {
color: red;
}

h2 {
color: blue;
}

/*! purgecss end ignore */
```

You can also safelist selectors directly in the declaration block as well:

```css
h3 {
/*! purgecss ignore current */
color: pink;
}
```

For further configuration, you can learn more [here](https://purgecss.com/configuration.html).
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,20 @@
"author": "AdrianGonz97",
"license": "MIT",
"dependencies": {
"chalk": "^5.3.0",
"css-tree": "^2.3.1",
"estree-walker": "^3.0.3",
"purgecss": "^6.0.0"
"fast-glob": "^3.3.2",
"purgecss": "^6.0.0",
"purgecss-from-html": "^6.0.0"
},
"devDependencies": {
"@changesets/cli": "^2.26.2",
"@types/css-tree": "^2.3.7",
"@types/estree": "^1.0.5",
"@types/node": "^18.11.18",
"prettier": "^2.8.1",
"tailwindcss": "^3.3.5",
"tsup": "^6.5.0",
"typescript": "^4.9.4",
"vite": "^5.0.0"
Expand All @@ -52,6 +59,7 @@
"dist"
],
"peerDependencies": {
"tailwindcss": "^3.3.0",
"vite": "^4.1.1 || ^5.0.0"
}
}
Loading

0 comments on commit 0f75105

Please sign in to comment.