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
- 531