Squeezing JS

I'm working on a website with hard requirements in SEO and performance, so I have to cut millisecond after millisecond to reduce the "Web Vitals" parameters (and in particular the so-called "Largest Contentful Paint"). And I've entered the magical world of automated tools for CSS optimization.

Two are the most interesting:

  • PurgeCSS permits to strip all unused CSS, and is very useful when you depend on Bootstrap (which of course includes lots of rules, most of which you don't use and probably don't even know). The dedicated Laravel Mix extension is even more useful, as uses your Blade templates to guess which rules you are using or not
  • CriticalCSS permits to identify and isolate the CSS rules used to render the initial part of your page, the first visualized by the user (and the crawler which evaluates your Vitals...). Also exists a Laravel Mix extension, but I've preferred to use the native NPM package

So I wanted to combine both of them to sum up the benefits: eliminate unused CSS, and put the critical one apart.

Here is my webpack.mix.js file:

const mix = require('laravel-mix');
require('laravel-mix-purgecss');

mix
    // Compile Sass, as usual
    .sass('resources/sass/app.scss', 'public/css')
    // Invoke functions to purge, minify, and extract source maps
    .purgeCss().sourceMaps().version().then(() => {
        if (mix.inProduction()) {
            // Remove the old versions. This is required to enforce inclusion of actual CSS, see the PHP sample code below
            let fse = require('fs-extra');
            fse.removeSync('public/css/app.css.critical.css');
            fse.removeSync('public/css/app.css.uncritical.css');

            const critical = require('critical');

            critical.generate({
                base: 'public/css/',
                // Access to the local instance to render and manipulate the CSS
                src: 'http://mywebsite.local.it/',
                width: 1920,
                height: 1080,
                target: {
                    css: 'app.css.critical.css',
                    uncritical: 'app.css.uncritical.css',
                },
                inline: false
            })
        }
    })

And this is the top part of my main Blade layout, extended by the actual template for each page:

@if(env('APP_DEBUG', false) == false)
    <?php

    $critical_path = public_path('css/app.css.critical.css');
    $uncritical_path = public_path('css/app.css.uncritical.css');

    ?>

    {{-- The reason to enforce CriticalCSS files removal before evaluation is here. If not removed, the old existing files are used to render the page, not the actual CSS --}}
    @if(file_exists($critical_path) && file_exists($uncritical_path))
        <style>
        {{-- The critical CSS is embedded directly into the page --}}
        {!! file_get_contents($critical_path) !!}
        </style>

        {{-- The uncritical CSS can be deferred at the end of loading --}}
        <link rel="stylesheet" href="{{ mix('/css/' . $css . '.uncritical.css') }}" media="print" onload="this.media='all';this.onload=null;" />
        <noscript><link rel="stylesheet" href="{{ mix('/css/' . $css . '.uncritical.css') }}" ></noscript>
    @else
        <link rel="preload" as="style" href="{{ mix('/css/app.css') }}" onload="this.onload=null;this.rel='stylesheet'">
    @endif
@else
    <link rel="preload" as="style" href="{{ mix('/css/app.css') }}" onload="this.onload=null;this.rel='stylesheet'">
@endif

If more page types are involved, with lot of different CSS rules required, it is convenient to use different Sass files and reiterate the critical.generate() part for each of them.

I have just to recompile eveything (with `npm run prod`) when not in debug mode, otherwise CSS from DebugBar is included.

Show Comments