Compiling and bundling JavaScript, the painless way

Michele Riva

Michele Riva

Senior Software Architect @NearForm

Google Developer Expert

Microsoft MVP

MicheleRivaCode

Real-World Next.js

Build scalable, high performances and modern web applications using Next.js, the React framework for production

MicheleRivaCode

Compiling and bundling JavaScript

is (often) a pain*

*but it shouldn't

MicheleRivaCode

A bit of terminology: compiling

to change a computer program into a machine language

Cambridge Dictionary

MicheleRivaCode

A bit of terminology: compiling transpiling

to translate a source code into a different language  source code

Me, myself

MicheleRivaCode

A bit of terminology: bundling

to encapsulate code and resources into a single executable file

Me, myself

MicheleRivaCode

Why do we want to transpile our code?

  • To make it compatible with different platforms
  • To write our scripts in different languages
  • To adopt new language features

MicheleRivaCode

MicheleRivaCode

Scala.js

https://www.scala-js.org

MicheleRivaCode

ReasonML

ReScript

F# (via Fable)

Gleam

Elm

Kotlin

Nim, Haxe, C/C++ (via Emscripten), ClojureScript, Dart, PureScript, Haskell (via GHCJS)

MicheleRivaCode

MicheleRivaCode

LLVM

MicheleRivaCode

function toUpper(x) {
  return x.toUpperCase();
}

function addExclamationMarks(x) {
  return x + '!!!'
}

function scream(input) {
  return input
    |> toUpper
    |> addExclamationMarks
    |> console.log
}

scream('Hello, Belgium');
// HELLO, BELGIUM!!!
"use strict";

function toUpper(x) {
  return x.toUpperCase();
}

function addExclamationMarks(x) {
  return x + '!!!';
}

function scream(input) {
  var _ref, _ref2, _input;

  return _ref = (
    _ref2 = (_input = input, toUpper(_input)),
    addExclamationMarks(_ref2)
  ), console.log(_ref);
}

scream('Hello, Belgium');
// HELLO, BELGIUM!!!

MicheleRivaCode

import { VFC } from 'react';

enum UserType {
  ADMIN     = "admin",
  EDITOR    = "editor",
  USER      = "user",
  ANONYMOUS = "guest"
}

type MyProps = {
  userType: UserType;
}

const MyComponent: VFC<MyProps> = ({ userType }) => {
  return (
    <div>
      User is of type: {props.userType}
    </div>
  )
}
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
var UserType;

(function (UserType) {
  UserType["ADMIN"] = "admin";
  UserType["EDITOR"] = "editor";
  UserType["USER"] = "user";
  UserType["ANONYMOUS"] = "guest";
})(UserType || (UserType = {}));

const MyComponent = ({ userType }) => {
  return /*#__PURE__*/ React.createElement(
    "div",
    null,
    "User is of type: ",
    props.userType
  );
};

MicheleRivaCode

Why do we want to bundle our code?

  • To create a single executable file
  • To serve a single JS file over the net

MicheleRivaCode

MicheleRivaCode

ReadArticle.jsx

NewArticle.jsx

AuthorProfile.jsx

BundledPage.js

MicheleRivaCode

in depths

transpiling

MicheleRivaCode

Language-to-language

ClojureScript

(defn simple-component []
  [:div
   [:p "I am a component!"]
   [:p.someclass
    "I have " [:strong "bold"]
    [:span {:style {:color "red"}} " and red "] "text."]])
cljs.user.simple_component = (function cljs$user$simple_component(){
  return new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE,
  [new cljs.core.Keyword(null,"div","div",(1057191632)),
  new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE,
  [new cljs.core.Keyword(null,"p","p",(151049309)),"I am a component!"], null),
  new cljs.core.PersistentVector(null, 5, 5, cljs.core.PersistentVector.EMPTY_NODE,
  [new cljs.core.Keyword(null,"p.someclass","p.someclass",(-1904646929)),
  "I have ",new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, 
  [new cljs.core.Keyword(null,"strong","strong",(269529000)),"bold"], null),
  new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, 
  [new cljs.core.Keyword(null,"span","span",(1394872991)),
  new cljs.core.PersistentArrayMap(null, 1, [new cljs.core.Keyword(null,"style","style",(-496642736)),
  new cljs.core.PersistentArrayMap(null, 1, [
  new cljs.core.Keyword(null,"color","color",(1011675173)),"red"]
  , null)], null)," and red "], null),"text."], null)], null);
});

https://reagent-project.github.io

MicheleRivaCode

[@bs.config {jsx: 3}];

module Greeting = {
  [@react.component]
  let make = () => {
    <button> {React.string("Hello!")} </button>
  };
};

ReactDOMRe.renderToElementWithId(<Greeting />, "preview");
// Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE
'use strict';

var React = require("react");
var ReactDOMRe = require("./stdlib/reactDOMRe.js");

function _none_$Greeting(Props) {
  return React.createElement("button", undefined, "Hello!");
}

var Greeting = {
  make: _none_$Greeting
};

ReactDOMRe.renderToElementWithId(React.createElement(_none_$Greeting, { }), "preview");

exports.Greeting = Greeting;
/*  Not a pure module */

https://reasonml.github.io

Language-to-language

ReasonML

MicheleRivaCode

Language-to-language

TypeScript

import { VFC } from 'react';

enum UserType {
  ADMIN     = "admin",
  EDITOR    = "editor",
  USER      = "user",
  ANONYMOUS = "guest"
}

type MyProps = {
  userType: UserType;
}

const MyComponent: VFC<MyProps> = ({ userType }) => {
  return (
    <div>
      User is of type: {props.userType}
    </div>
  )
}
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
var UserType;

(function (UserType) {
  UserType["ADMIN"] = "admin";
  UserType["EDITOR"] = "editor";
  UserType["USER"] = "user";
  UserType["ANONYMOUS"] = "guest";
})(UserType || (UserType = {}));

const MyComponent = ({ userType }) => {
  return /*#__PURE__*/ React.createElement(
    "div",
    null,
    "User is of type: ",
    props.userType
  );
};

MicheleRivaCode

Every language has its own transpiler

TypeScript TSC

Fable

ClojureScript

BuckleScript

JavaScript (Babel)

MicheleRivaCode

Problem #1

transpilation time

Really fast

Average

Fast

Slow on large codebases

Slow on large codebases

MicheleRivaCode

Problem #2

optimized output

Beautifully optimized

Awful

Beautifully optimized

Quite optimized

Well optimized*

MicheleRivaCode

Let's focus on the most popular ones

Quite slow, quite optimized

Quite fast, well optimized*

MicheleRivaCode

Bundling time grows

Quite slow, quite optimized

Quite fast, well optimized

MicheleRivaCode

MicheleRivaCode

in depths

bundling

WebPack

Parcel

Rollup

MicheleRivaCode

Hard

Easier

Easiest

WebPack

Parcel

Rollup

MicheleRivaCode

Hard

Easier

Easiest

Slowest

Slower

Fast

WebPack

Parcel

Rollup

MicheleRivaCode

king of configurations

MicheleRivaCode

king of configurations

Is it still worth it?

MicheleRivaCode

ESBuild

SWC

Vite

Snowpack

is there any better alternative?

(rest in pepperoni)

MicheleRivaCode

esbuild src/myEntry.js --bundle --sourcemap --minify --outfile=dist/mybundle.js

ESBuild

MicheleRivaCode

ES2019

ES2020

MicheleRivaCode

{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false,
      "dynamicImport": false,
      "privateMethod": false,
      "functionBind": false,
      "exportDefaultFrom": false,
      "exportNamespaceFrom": false,
      "decorators": false,
      "decoratorsBeforeExport": false,
      "topLevelAwait": false,
      "importMeta": false
    },
    "transform": null,
    "target": "es5",
    "loose": false,
    "externalHelpers": false,
    // Requires v1.2.50 or upper and requires target to be es2016 or upper.
    "keepClassNames": false
  }
}

MicheleRivaCode

SWC can run in a browser thanks to WASM

MicheleRivaCode

Vite

MicheleRivaCode

Vite

MicheleRivaCode

Vite

MicheleRivaCode

// ESM
export default function greet() {
  return "Hello Serbia!";
}

// ESM
import foo, { bar } from "foobar";
// CJS
module.exports = function greet() {
  return "Hello Serbia!";
}

// CJS
const foo, { bar } = require("foobar");

MicheleRivaCode

Vite

MicheleRivaCode

MicheleRivaCode

rest in pepperoni

Snowpack

MicheleRivaCode

Snowpack

rest in pepperoni

MicheleRivaCode

Snowpack

rest in pepperoni

MicheleRivaCode

MicheleRivaCode

/*
 * Skypack CDN - canvas-confetti@1.4.0
 *
 * Learn more:
 *   📙 Package Documentation: https://www.skypack.dev/view/canvas-confetti
 *   📘 Skypack Documentation: https://www.skypack.dev/docs
 *
 * Pinned URL: (Optimized for Production)
 *   ▶️ Normal: https://cdn.skypack.dev/pin/canvas-confetti@v1.4.0-POmgSMO0U5q84otJfYlN/mode=imports/optimized/canvas-confetti.js
 *   ⏩ Minified: https://cdn.skypack.dev/pin/canvas-confetti@v1.4.0-POmgSMO0U5q84otJfYlN/mode=imports,min/optimized/canvas-confetti.js
 *
 */

// Browser-Optimized Imports (Don't directly import the URLs below in your application!)
export * from '/-/canvas-confetti@v1.4.0-POmgSMO0U5q84otJfYlN/dist=es2020,mode=imports/optimized/canvas-confetti.js';
export {default} from '/-/canvas-confetti@v1.4.0-POmgSMO0U5q84otJfYlN/dist=es2020,mode=imports/optimized/canvas-confetti.js';

MicheleRivaCode

MicheleRivaCode

The future is no-bundle

MicheleRivaCode

The future is no-bundle*

*maybe

MicheleRivaCode

The future is bright

MicheleRivaCode

https://kdy1.dev/posts/2022/1/tsc-go

MicheleRivaCode

MicheleRivaCode

MicheleRivaCode

@MicheleRiva

@MicheleRivaCode

/in/MicheleRiva95

www.micheleriva.dev

Compiling and bundling JS, the painless way

By Michele Riva

Compiling and bundling JS, the painless way

  • 353