DEV Community

loading...

Reduce Angular style size (using PurgeCSS to remove unused styles)

dylanvdmerwe profile image Dylan v.d Merwe ・6 min read

We want to keep our bundle sizes as low as possible right? I have been investigating how to do this, specifically around the CSS files output by Angular projects.

Wait, doesn't Angular do this already? Not really. Angular will put styles from your components directly into the .js files, and all third-party, library or global styles go into a dedicated styles.css in the /dist folder. It won't remove any unused styles automatically.

Alt Text

I want to investigate some other options, and then show you the solution I ended up implementing on some of our bigger projects to show you the savings.

What options are there?

VSCode plugin

We can use some plugins in our IDE to help identify styles that are not used.

For example there is Unused CSS Clases for JavaScript/Angular/React.

Alt Text

Although very useful while building pages and components it does have some drawbacks with Angular and SASS such as not correctly working with SASS mixins, understanding class bindings [class.highlighted]="highlight", or working out what is going on inside of ::ng-deep.

I do recommend doing development with this plugin installed so that you can easily pick up in your style sheets potential unused style classes that you can clean up as you go.

ngx-unused-css

There is an npm package called ngx-unused-css that, when installed and run on your project, will scan your files and provide a list of all styles it deems are not used.

Alt Text

I found this hard to work through in a bigger project, hence why I logged a potential feature request to help. Probably more useful for smaller projects that do not have many components/pages.

Which brings us to the solution I ended up implementing...

PurgeCSS

PurceCSS is a well-known tool that scans the output of the built CSS files and will use it's heuristics and extractors to remove unused CSS - predominantly brought to fame thanks to Tailwind.

When working in big projects, often with multiple team members or contributors, it is hard to keep track over time when styles are no longer in use. This bloats the CSS that is shipped to the browser which, while not as dramatic as large JavaScript files, does contribute towards higher data use and CPU time to parse.

Getting PurgeCSS to work alongside Angular and the Angular CLI is quite a challenge for various reasons. I investigated numerous ways:

If these work for you that is awesome. But I needed something that would fit into my project workflow and not be as intrusive.

So I wrote my own npm postbuild script

In my package.json, I have the following scripts:

{
  "name": "test-app",
  "version": "1.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "lint": "tslint src/**/*.ts --config tslint.json --project tsconfig.json ",
    "lint:fix": "tslint src/**/*.ts --config tslint.json --fix --project tsconfig.json",
    "prebuild": "node environments/prebuild.ts",
    "postbuild": "node environments/postbuild.js",
    "update-angular": "ng update @angular/cli @angular/core --allow-dirty",
  },
Enter fullscreen mode Exit fullscreen mode

You can learn about npm scripts here. Note the postbuild script - npm will run this script once the build step is complete. The build step just runs ng build as defined above. The postbuild script does not run while we are doing development with ng serve.

What I want the postbuild script to do is to perform the PurgeCSS step, replace any style files that it has changed and then provide an output of any file size differences.

In my environment, our tooling runs npm run build and npm run build -- --prod when deploying to the dev or production environments respectively. Now with the postbuild script included, the PurgeCSS step will happen right after the build completes and before the deployment of any files.

The postbuild.js script

Note that this has been tested on Windows.

const exec = require('child_process').exec;
const fs = require('fs');
const path = require('path');

// find the styles css file
const files = getFilesFromPath('./dist', '.css');
let data = [];

if (!files && files.length <= 0) {
  console.log("cannot find style files to purge");
  return;
}

for (let f of files) {
  // get original file size
  const originalSize = getFilesizeInKiloBytes('./dist/' + f) + "kb";
  var o = { "file": f, "originalSize": originalSize, "newSize": "" };
  data.push(o);
}

console.log("Run PurgeCSS...");

exec("purgecss -css dist/*.css --content dist/index.html dist/*.js -o dist/", function (error, stdout, stderr) {
  console.log("PurgeCSS done");
  console.log();

  for (let d of data) {
    // get new file size
    const newSize = getFilesizeInKiloBytes('./dist/' + d.file) + "kb";
    d.newSize = newSize;
  }

  console.table(data);
});

function getFilesizeInKiloBytes(filename) {
  var stats = fs.statSync(filename);
  var fileSizeInBytes = stats.size / 1024;
  return fileSizeInBytes.toFixed(2);
}

function getFilesFromPath(dir, extension) {
  let files = fs.readdirSync(dir);
  return files.filter(e => path.extname(e).toLowerCase() === extension);
}
Enter fullscreen mode Exit fullscreen mode

Make sure to run npm i -D purgecss to install the right dependency.

Let's see some examples

All of these projects are using Angular 11.

Medium-size project

This is the output of a medium sized project that includes Bootstrap and has multiple style sheets for different clients.

Run PurgeCSS...
PurgeCSS done

┌─────────┬───────────────────────────────────┬──────────────┬───────────┐
│ (index) │               file                │ originalSize │  newSize  │
├─────────┼───────────────────────────────────┼──────────────┼───────────┤
│    0    │       'benefitexchange.css'       │  '148.48kb'  │ '36.89kb' │
│    1    │       'creativecounsel.css'       │  '148.46kb'  │ '36.89kb' │
│    2    │        'fibrecompare.css'         │  '148.35kb'  │ '36.77kb' │
│    3    │          'filledgap.css'          │  '148.07kb'  │ '36.68kb' │
│    4    │            'hippo.css'            │  '148.43kb'  │ '36.86kb' │
│    5    │          'klicknet.css'           │  '148.49kb'  │ '36.89kb' │
│    6    │            'mondo.css'            │  '148.44kb'  │ '36.87kb' │
│    7    │           'nedbank.css'           │  '148.63kb'  │ '37.05kb' │
│    8    │         'phonefinder.css'         │  '148.41kb'  │ '36.85kb' │
│    9    │       'realpromotions.css'        │  '148.46kb'  │ '36.88kb' │
│   10    │ 'styles.428c935b7c11a505124a.css' │  '33.96kb'   │ '20.33kb' │
└─────────┴───────────────────────────────────┴──────────────┴───────────┘
Enter fullscreen mode Exit fullscreen mode

There are some dramatic savings where huge chunks of unused Bootstrap and other library styles are removed.

Large enterprise project

This is the output of a large enterprise project that makes use of https://github.com/NG-ZORRO/ng-zorro-antd. There are plenty of other dependencies such as ngx-dropzone, lightgallery.js, ngx-toastr, web-social-share, etc.

There were some issues with some components in NG-ZORRO such as the date picker and the steps component not working at run time. These will need to be raised with the project maintainers to hopefully resolve - otherwise you may need to use other libraries.

Run PurgeCSS...
PurgeCSS done

┌─────────┬───────────────────────────────────┬──────────────┬────────────┐
│ (index) │               file                │ originalSize │  newSize   │
├─────────┼───────────────────────────────────┼──────────────┼────────────┤
│    0    │ 'styles.72918fcbd85fee3bb2a8.css' │  '545.01kb'  │ '231.97kb' │
└─────────┴───────────────────────────────────┴──────────────┴────────────┘
Enter fullscreen mode Exit fullscreen mode

Again, a huge reduction thanks to unused styles being removed.

Some caveats

Make sure you test your code.

In my experience, the less complicated the project the better this approach will work. Once you start using more complicated UI tools and packages (such as NG-ZORRO or Angular Material) you may run into situations where PurgeCSS is not able to determine styles that are used due to run-time interactions on components and these styles will be stripped, causing some very weird looking sites.

Also note that solution will only work on CSS files, it will not remove any styles that Angular builds into your .js files from your components. Now that would take this to the next level - but I think think would require more integration with Webpack and the Angular CLI.

Conclusion

I was struggling to manually identify and remove unused styles in my company's Angular projects in order to 1) keep bloat down and 2) ensure the site is as lean as possible.

By implementing a postbuild npm script that runs PurgeCSS on the build output, we have seen some dramatic reductions in style size. I do recommend cleaning up your own CSS or SASS files as the primary measure.

Overall the majority of projects worked without any changes, but your mileage may vary.

I would love to know if this solution works for you, what size reductions you are getting or if you have any other thoughts on this.

Discussion (1)

pic
Editor guide
Collapse
daviddalbusco profile image
David Dal Busco

Thank your for the share Dylan! Not in an Angular project but, I've got an issue/reminder open since a couple of months about purgeCSS. Now I know where to begin 👍