Optimise your Frontend CI/CD pipeline with Go and ESBuild

ESBuild is one of the fastest bundler/compilers for the Frontend libraries. Written in Golang, Built for front-end apps.

Featured on Hashnode

Subscribe to my newsletter and never miss my upcoming articles

If you heard of the Snowpack, You may be familiar with ESBuild. ESBuild is one of the fastest bundler/compilers for the Frontend libraries. ESBuild is not a bundler for Reactjs, It can bundle any JavaScript modules. ESBuild is written in Golang, which make is fast. ESBuild is not a complete solution for your all need. However, Implementing it in your pipeline significantly can improve your team productivity and deployment build time.

Prerequisite:

  1. Go [v1.6 and above]
  2. NPM (nodejs package manager)

Note: ESBuild is also available in nodejs. However, I find using Go and create binary CLI is much faster and reusable.

Why should I use ESBuild when I have the webpack?

There is no reason that you should stop using webpack. But there are a lot of reasons you can at least start thinking of using ESBuild. Some of the best ones are given below. For more info, you can visit the official page.

  1. It's written in Go and compiles to native code.
  2. Parallelism is used heavily.
  3. Memory is used efficiently.
  4. Loaders and plugins added by default

[Compilers time comparison]

Compilers time comparison

Where to start

To build a pipeline, You need an application. You can start a project from scratch as it is documented here. However, For demo purpose, I will use the Quote of the day app that I have created in Github.

Project Structure

Project Structure

  • public: A place to keep all public file like image
  • src: It contains the source code written in TypeScript and ReactJS
  • package.json: This will contain all the common scripts and dependencies for the nodejs app
  • runner.go: The build script is written in Golang to use ESBuild to compile and bundle typescript
  • tsconfig.json: Basic configuration for a TypeScript and react project.

The rest of the files and folder you can ignore for now.

Note: The purpose of the article to show how to user ESBuild. So I am not going into the details of react app. You can find source code here in Quote of the day repo.

React App Code

[Screenshot of code]

Write your first Go Script

Open runner.go and add below code

package main

import (
    "os"

    "github.com/evanw/esbuild/pkg/api"
)

func main() {
    result := api.Build(api.BuildOptions{
        EntryPoints: []string{"src/app.tsx"},
        Outdir:      "dist",
        Bundle:      true,
        Write:       true,
        LogLevel:    api.LogLevelInfo,
    })
    if len(result.Errors) > 0 {
        os.Exit(1)
    }
}

Create go module and get all go dependencies

## Create/init a go module
go mod init quote-of-day 

## Create vendor module/directory for all dependencies(similar to **node_modules**)
go mod vendor 

## Get all go dependencies
go mod tidy

Once you are done with go modules, Let's update package.json to build go binary runner.

/*package.json*/

"scripts": {
    "build:bin": "go build -o runner.exe runner.go",
    "build": "./runner.exe && cp public/* dist/",
    "serve": "http-server dist",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Explanation: npm run build:bin will create an executable binary(runner.exe). This will we used to compile react source code. To compile react code next time you can use npm run build.

Note: We can use the go run command directly. However, Binary will be much faster and can be distributed among the teams.

Run Build: Once you run npm run build, You will see the output as below. You will see some warnings, We will fix these later.

Things to be noted, The size of the app.js. It will be around 1mb which is too high for such a small app.

Build output

Optimise build settings and Fix warning messages

To optimize the es build settings, Let's add below BuildOptions in runner.go

//... rest of the code

func main() {
    result := api.Build(api.BuildOptions{
        EntryPoints:       []string{"src/app.tsx"},
        Outdir:            "dist",
        Bundle:            true,
        Write:             true,
        LogLevel:          api.LogLevelInfo,
        ChunkNames:        "chunks/[name]-[hash]",
        MinifyWhitespace:  true,
        MinifyIdentifiers: true,
        MinifySyntax:      true,
        Splitting:         true,
        Format:            api.FormatESModule,
        Color:             api.ColorAlways,
        Define: map[string]string{
            "process.env.NODE_ENV": `"dev"`,
        },
        AssetNames: "assets/[name]-[hash]",
        Loader: map[string]api.Loader{
            ".png": api.LoaderFile,
        },
        Engines: []api.Engine{
            {api.EngineChrome, "58"},
            {api.EngineFirefox, "57"},
            {api.EngineSafari, "11"},
            {api.EngineEdge, "16"},
        },
    })
    if len(result.Errors) > 0 {
        os.Exit(1)
    }
}

Now re-build runner.exe and re-run the build with npm run build:bin && npm run build commands. This time you won't see any error or warning. Same time, The size of the app.js will be reduced to 283.4kb.

Quote Of the Day application

To see the output, I have added http-server in the devDependencies. You can use any other static server.

npm run serve

[Quote Of the Day App Demo]

App Demo

Comparison with Webpack

I tried to compare build time with Webpack with bare minimal configuration. For that we do need to add some dev dependencies and Webpack itself.

Add dependencies: Add webpack and some loaders

npm install --save-dev css-loader style-loader webpack webpack-cli typescript ts-loader

Update package scripts: Add new command to run build using webpack

"scripts": {
    ...
    "webpack": "webpack && cp public/* dist/",
   ...
  },

Create a webpack configuration file:

// webpack.config.js

const path = require("path");

module.exports = {
  entry: "./src/app.tsx",
  mode: "production",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  output: {
    filename: "app.js",
    path: path.resolve(__dirname, "dist"),
  },
};

Let's run both commands and compare:

  • Run ESBuild: npm run build
  • Run Webpack: npm run webpack

ESBuild vs Webpack

The time taken by Webpack v/s ESbuild is 5.387s/0.329s. As you can clearly see the difference between Webpack vs ESBuild time. ESBuild is 16x faster than Webpack.

Conclusion

ESBuild is not one solution for all your needs. In my opinion, ESBuild should not be used for development purpose. It is not a complete tool. You may struggle to enable a feature like live reload. For that, you can use Snowpack which is built on top of ESBuild APIs. However, by Optimizing build configs and proper scripting pipeline, You can optimize your CI/CD pipeline significantly.

There is a nice quote by Coco Chanel

“To be irreplaceable, one must always be different ” – Coco Chanel

Code: You can all code in es-build-react GitHub repo. And webpack version of the code can be found in esbuild_vs_webpack branch of Github repo.


If you like and appreciate my hours of effort to research, write code and write this article. Please share your love and reaction ;) 😊. Thanks in advance.

No Comments Yet