javascriptcssnpmsassesbuild

How To Setup Custom ESBuild with SCSS, PurgeCSS & LiveServer?


Background:

I have a Webpack setup that I use to preprocess SCSS with PurgeCSS with a live HMR server with esbuild-loader for speeding up compiles in Webpack but even then my compile times are still slow and I would like the raw-speed of ESBuild and remove Webpack setup altogether.

The basic setup of ESBuild is easy, you install esbuild using npm and add the following code in your package.json:

{
  ...
  "scripts": {
    ...
    "watch": "esbuild --bundle src/script.js --outfile=dist/script.js --watch"
  },
  ...
}

and run it by using the following command:

npm run watch

This single-line configuration will bundle your scripts and styles (you can import style.css in script.js) and output the files in the dist directory but this doesn't allow advance configuration for ESBuild like outputting a different name for your stylesheet and script files or using plugins.

Problems:

  1. How to configure ESBuild using an external config file?
  2. ESBuild doesn't support SCSS out-of-the-box. How to configure external plugins like esbuild-sass-plugin and to go even further, how to setup PostCSS and its plugins like Autoprefixer?
  3. How to setup dev server with auto-rebuild?
  4. How to setup PurgeCSS?

Solution

  • Solutions:

    1. How to configure ESBuild using an external config file?

    1. Create a new file in root: esbuild.mjs with the following contents:
    import esbuild from "esbuild";
    
    esbuild
        .build({
            entryPoints: ["src/styles/style.css", "src/scripts/script.js"],
            outdir: "dist",
            bundle: true,
            plugins: [],
        })
        .then(() => console.log("⚡ Build complete! ⚡"))
        .catch(() => process.exit(1));
    
    1. Add the following code in your package.json:
    {
        ...
        "scripts": {
            ...
            "build": "node esbuild.mjs"
        },
    ...
    }
    
    1. Run the build by using npm run build command and this would bundle up your stylesheets and scripts and output them in dist directory.
    2. For more details and/or adding custom build options, please refer to ESBuild's Build API documentation.

    2. ESBuild doesn't support SCSS out-of-the-box. How to configure external plugins like esbuild-sass-plugin and to go even further, how to setup PostCSS and plugins like Autoprefixer?

    1. Install npm dependencies: npm i -D esbuild-sass-plugin postcss autoprefixer
    2. Edit your esbuild.mjs to the following code:
    import esbuild from "esbuild";
    import { sassPlugin } from "esbuild-sass-plugin";
    import postcss from 'postcss';
    import autoprefixer from 'autoprefixer';
    
    // Generate CSS/JS Builds
    esbuild
        .build({
            entryPoints: ["src/styles/style.scss", "src/scripts/script.js"],
            outdir: "dist",
            bundle: true,
            metafile: true,
            plugins: [
                sassPlugin({
                    async transform(source) {
                        const { css } = await postcss([autoprefixer]).process(source);
                        return css;
                    },
                }),
            ],
        })
        .then(() => console.log("⚡ Build complete! ⚡"))
        .catch(() => process.exit(1));
    

    3. How to setup dev server with auto-rebuild?

    1. ESBuild has a limitation on this end, you can either pass in watch: true or run its server. It doesn't allow both.
    2. ESBuild also has another limitation, it doesn't have HMR support like Webpack does.
    3. So to live with both limitations and still allowing a server, we can use Live Server. Install it using npm i -D @compodoc/live-server.
    4. Create a new file in root: esbuild_watch.mjs with the following contents:
    import liveServer from '@compodoc/live-server';
    import esbuild from 'esbuild';
    import { sassPlugin } from 'esbuild-sass-plugin';
    import postcss from 'postcss';
    import autoprefixer from 'autoprefixer';
    
    // Turn on LiveServer on http://localhost:7000
    liveServer.start({
        port: 7000,
        host: 'localhost',
        root: '',
        open: true,
        ignore: 'node_modules',
        wait: 0,
    });
    
    // Generate CSS/JS Builds
    esbuild
        .build({
            logLevel: 'debug',
            metafile: true,
            entryPoints: ['src/styles/style.scss', 'src/scripts/script.js'],
            outdir: 'dist',
            bundle: true,
            watch: true,
            plugins: [
                sassPlugin({
                    async transform(source) {
                        const { css } = await postcss([autoprefixer]).process(
                            source
                        );
                        return css;
                    },
                }),
            ],
        })
        .then(() => console.log('⚡ Styles & Scripts Compiled! ⚡ '))
        .catch(() => process.exit(1));
    
    1. Edit the scripts in your package.json:
    {
        ...
        "scripts": {
            ...
            "build": "node esbuild.mjs",
            "watch": "node esbuild_watch.mjs"
        },
    ...
    }
    
    1. To run build use this command npm run build.
    2. To run dev server with auto-rebuild run npm run watch. This is a "hacky" way to do things but does a fair-enough job.

    4. How to setup PurgeCSS?

    I found a great plugin for this: esbuild-plugin-purgecss by peteryuan but it wasn't allowing an option to be passed for the html/views paths that need to be parsed so I created esbuild-plugin-purgecss-2 that does the job. To set it up, read below:

    1. Install dependencies npm i -D esbuild-plugin-purgecss-2 glob-all.
    2. Add the following code to your esbuild.mjs and esbuild_watch.mjs files:
    // Import Dependencies
    import glob from 'glob-all';
    import purgecssPlugin2 from 'esbuild-plugin-purgecss-2';
    
    esbuild
        .build({
            plugins: [
                ...
                purgecssPlugin2({
                    content: glob.sync([
                        // Customize the following URLs to match your setup
                        './*.html',
                        './views/**/*.html'
                    ]),
                }),
            ],
        })
        ...
    
    1. Now running the npm run build or npm run watch will purgeCSS from the file paths mentioned in glob.sync([...] in the code above.

    TL;DR:

    1. Create an external config file in root esbuild.mjs and add the command to run it in package.json inside scripts: {..} e.g. "build": "node esbuild.mjs" to reference and run the config file by using npm run build.
    2. ESBuild doesn't support HMR. Also, you can either watch or serve with ESBuild, not both. To overcome, use a separate dev server library like Live Server.
    3. For the complete setup, please refer to my custom-esbuild-with-scss-purgecss-and-liveserver repository on github.

    Final Notes:

    I know this is a long thread but it took me a lot of time to figure these out. My intention is to have this here for others looking into the same problems and trying to figure out where to get started.

    Thanks.