diff --git a/src/index.html b/src/index.html
index 4749f81..55a20c6 100644
--- a/src/index.html
+++ b/src/index.html
@@ -12,7 +12,8 @@
sunset glow / under construction :)
posts:
diff --git a/src/posts/frontend-build-systems.tex b/src/posts/frontend-build-systems.tex
index e6d8731..1a9c0d9 100644
--- a/src/posts/frontend-build-systems.tex
+++ b/src/posts/frontend-build-systems.tex
@@ -1,5 +1,8 @@
\documentclass{article}
\usepackage{enumitem}
+\newcommand{\ti}{\textit}
+\newcommand{\tb}{\textbf}
+\newcommand{\tc}{\texttt}
\title{Frontend Build Systems}
\begin{document}
@@ -13,26 +16,26 @@
JavaScript source code directly to the client leads to two primary problems:
\begin{enumerate}
- \item \textbf{Unsupported Language Features:} Because JavaScript runs in the browser, and there
- are many browsers out there of a variety of versions, each language feature you use reduces the
- number of clients that can execute your JavaScript. Furthermore, language extensions like JSX
- are not valid JavaScript and will not run in any browser.
+ \item \tb{Unsupported Language Features:} Because JavaScript runs in the browser, and because
+ there are many browsers out there of a variety of versions, each language feature you use
+ reduces the number of clients that can execute your JavaScript. Furthermore, language extensions
+ like JSX are not valid JavaScript and will not run in any browser.
- \item \textbf{Performance:} The browser must request each JavaScript file individually. In a large
+ \item \tb{Performance:} The browser must request each JavaScript file individually. In a large
codebase, this can result in thousands of HTTP requests in order to render a single page. In the
past, prior to HTTP/2, this would also result in thousands of TLS handshakes.
In addition, several sequential network round trips may be needed before all the JavaScript is
- loaded. For example, if \texttt{index.js} imports \texttt{page.js} and \texttt{page.js} imports
- \texttt{button.js}, three sequential network round trips are necessary to fully load the
- JavaScript. This is called the waterfall problem.
+ loaded. For example, if \tc{index.js} imports \texttt{page.js} and \texttt{page.js} imports
+ \tc{button.js}, three sequential network round trips are necessary to fully load the JavaScript.
+ This is called the waterfall problem.
Source files can also be unnecessarily large due to long variable names and whitespace
indentation characters, increasing bandwidth usage and network loading time.
\end{enumerate}
Frontend build systems process source code and emit one or more JavaScript files which are optimized
-for sending to the browser. The resulting \textit{distributable} is typically illegible to humans.
+for sending to the browser. The resulting \ti{distributable} is typically illegible to humans.
\section{Build Steps}
@@ -66,21 +69,21 @@ \subsection{Transpilation}
The transpilers in common use today are Babel, SWC, and TypeScript Compiler.
\begin{enumerate}
- \item \href{https://babeljs.io/}{\textbf{Babel}} (2014) is the standard transpiler: a slow
+ \item \href{https://babeljs.io/}{\tb{Babel}} (2014) is the standard transpiler: a slow
single-threaded transpiler written in JavaScript. Many frameworks and libraries that require
transpilation do so via a Babel plugin, requiring Babel to be part of the build process.
However, Babel is hard to debug and can often be confusing.
- \item \href{https://swc.rs/}{\textbf{SWC}} (2020) is a fast multi-threaded transpiler written in
- Rust. It claims to be 20x faster than Babel; hence, it is used by the newer frameworks and build
+ \item \href{https://swc.rs/}{\tb{SWC}} (2020) is a fast multi-threaded transpiler written in Rust.
+ It claims to be 20x faster than Babel; hence, it is used by the newer frameworks and build
tools. It supports transpiling TypeScript and JSX. If your application does not require Babel,
SWC is a superior choice.
- \item \href{https://github.com/microsoft/TypeScript}{\textbf{TypeScript Compiler (tsc)}} also
- supports transpiling TypeScript and JSX. It is the reference implementation of TypeScript and
- the only fully featured TypeScript type checker. However, it is very slow. While a TypeScript
- application must typecheck with the TypeScript Compiler, for its build step, an alternative
- transpiler will be much more performant.
+ \item \href{https://github.com/microsoft/TypeScript}{\tb{TypeScript Compiler (tsc)}} also supports
+ transpiling TypeScript and JSX. It is the reference implementation of TypeScript and the only
+ fully featured TypeScript type checker. However, it is very slow. While a TypeScript application
+ must typecheck with the TypeScript Compiler, for its build step, an alternative transpiler will
+ be much more performant.
\end{enumerate}
It is also possible to skip the transpilation step if your code is pure JavaScript and uses ES6
@@ -107,10 +110,10 @@ \subsection{Bundling}
The bundlers in common use today are Webpack, Parcel, Rollup, esbuild, and Turbopack.
\begin{enumerate}
- \item \href{https://webpack.js.org/}{\textbf{Webpack}} (2014) started gaining significant
- popularity around 2016, later becoming the standard bundler. Unlike the then-incumbent
- Browserify, which was commonly used with the Gulp task runner, Webpack pioneered ``loaders'' that
- transformed source files upon import, allowing Webpack to orchestrate the entire build pipeline.
+ \item \href{https://webpack.js.org/}{\tb{Webpack}} (2014) started gaining significant popularity
+ around 2016, later becoming the standard bundler. Unlike the then-incumbent Browserify, which
+ was commonly used with the Gulp task runner, Webpack pioneered ``loaders'' that transformed
+ source files upon import, allowing Webpack to orchestrate the entire build pipeline.
Loaders also allowed developers to transparently import static assets inside JavaScript files,
combining all source files and static assets into a single dependency graph. With Gulp, each
@@ -120,26 +123,26 @@ \subsection{Bundling}
Webpack is slow and single-threaded, written in JavaScript. It is highly configurable, but its
many configuration options can be confusing.
- \item \href{https://rollupjs.org/}{\textbf{Rollup}} (2016) capitalized on the widespread browser
+ \item \href{https://rollupjs.org/}{\tb{Rollup}} (2016) capitalized on the widespread browser
support of ES6 Modules and the optimizations it enabled, namely tree shaking. It produced far
smaller bundle sizes than Webpack, leading Webpack to later adopt similar optimizations. Rollup
is a single-threaded bundler written in JavaScript, only slightly more performant than Webpack.
- \item \href{https://parceljs.org/}{\textbf{Parcel}} (2018) is a low-configuration bundler designed
- to ``just work'' out of the box, providing sensible default configurations for all steps of the
+ \item \href{https://parceljs.org/}{\tb{Parcel}} (2018) is a low-configuration bundler designed to
+ ``just work'' out of the box, providing sensible default configurations for all steps of the
build process and developer tooling needs. Parcel 2 uses SWC under the hood. It is much faster
than Webpack and Rollup.
- \item \href{https://esbuild.github.io/}{\textbf{Esbuild}} (2020) is a bundler architected for
+ \item \href{https://esbuild.github.io/}{\tb{Esbuild}} (2020) is a bundler architected for
parallelism and optimal performance. It is dozens of times more performant than Webpack, Rollup,
and Parcel 2. Esbuild implements a basic transpiler as well as a minifier. However, it is less
featureful than the other bundlers, providing a limited plugin API that cannot directly modify
- the AST. Instead of modifying source files with an esbuild plugins, the files can be transformed
+ the AST. Instead of modifying source files with an esbuild plugin, the files can be transformed
prior to being passed to esbuild.
- \item \href{https://turbo.build/pack}{\textbf{Turbopack}} (2022) is a fast Rust bundler that
- supports incremental rebuilds. The project is built by Vercel and is led by the creator of
- Webpack. It is currently in beta and may be opted-into in Next.js.
+ \item \href{https://turbo.build/pack}{\tb{Turbopack}} (2022) is a fast Rust bundler that supports
+ incremental rebuilds. The project is built by Vercel and led by the creator of Webpack. It is
+ currently in beta and may be opted-into in Next.js.
\end{enumerate}
It is possible to skip the bundling step if you have very few modules or have very low network
@@ -152,7 +155,7 @@ \subsubsection{Code Splitting}
applications with many pages and features, the bundle can be very large, negating the original
performance benefits of bundling.
-Dividing the bundle into several smaller bundles, or \textit{code splitting}, solves this problem. A
+Dividing the bundle into several smaller bundles, or \ti{code splitting}, solves this problem. A
common approach is to split each page into a separate bundle. With HTTP/2, shared dependencies may
also be factored out into their own bundles to avoid duplication at little cost. Additionally, large
modules may split into a separate bundle and lazy-loaded on-demand.
@@ -165,27 +168,27 @@ \subsubsection{Code Splitting}
creates separate bundles per page, only including the code imported by that page in its bundles.
Loading a page preloads all bundles used by that page in parallel. This optimizes bundle size
without re-introducing the waterfall problem. The filesystem router achieves this by creating one
-entry point per page (\texttt{pages/**/*.jsx}), as opposed to the single entry point of traditional
-client side React apps (\texttt{index.jsx}).
+entry point per page (\tc{pages/**/*.jsx}), as opposed to the single entry point of traditional
+client side React apps (\tc{index.jsx}).
\subsubsection{Tree Shaking}
A bundle is composed of multiple modules, each of which contains one or more exports. Often, a given
bundle will only make use of a subset of exports from the modules it imports. The bundler can remove
-the unused exports of its modules in a process called \textit{tree shaking}. This optimizes the
-bundle size, improving loading and parsing times.
+the unused exports of its modules in a process called \ti{tree shaking}. This optimizes the bundle
+size, improving loading and parsing times.
Tree shaking depends on static analysis of the source files, and is thus impeded when static
analysis is made more challenging. Two primary factors influence the efficiency of tree shaking:
\begin{enumerate}
- \item \textbf{Module System:} ES6 Modules have static exports and imports, while CommonJS modules
- have dynamic exports and imports. Bundlers are thus able to be more aggressive and efficient
- when tree shaking ES6 Modules.
+ \item \tb{Module System:} ES6 Modules have static exports and imports, while CommonJS modules have
+ dynamic exports and imports. Bundlers are thus able to be more aggressive and efficient when
+ tree shaking ES6 Modules.
- \item \textbf{Side Effects:} The \texttt{sideEffects} property of \texttt{package.json} declares
- whether a module has side effects on import. When side effects are present, unused modules and
- unused exports may not be tree shaken due to the limitations of static analysis.
+ \item \tb{Side Effects:} The \tc{sideEffects} property of \texttt{package.json} declares whether a
+ module has side effects on import. When side effects are present, unused modules and unused
+ exports may not be tree shaken due to the limitations of static analysis.
\end{enumerate}
\subsubsection{Static Assets}
@@ -196,7 +199,7 @@ \subsubsection{Static Assets}
Prior to Webpack, static assets were built separately from the source code in the build pipeline, in
an independent build task. To load the static assets, the application had to reference them by their
final path in the distributable. Thus, it was common to carefully organize assets around a URL
-convention (e.g. \texttt{/assets/css/banner.jpg} and \texttt{/assets/fonts/Inter.woff2}).
+convention (e.g. \tc{/assets/css/banner.jpg} and \texttt{/assets/fonts/Inter.woff2}).
Webpack ``loaders'' allowed the importing of static assets from JavaScript, unifying both code and
static assets into a single dependency graph. During bundling, Webpack replaces the static asset
@@ -218,16 +221,15 @@ \subsection{Minification}
at the end of the build process.
Several JavaScript minifiers in common use today are Terser, esbuild, and SWC.
-\href{https://terser.org/}{\textbf{Terser}} was forked from the unmaintained uglify-es. It is
-written in JavaScript and is somewhat slow. \textbf{Esbuild} and \textbf{SWC}, mentioned previously,
-implement minifiers in addition to their other capabilities and are faster than Terser.
+\href{https://terser.org/}{\tb{Terser}} was forked from the unmaintained uglify-es. It is written in
+JavaScript and is somewhat slow. \tb{Esbuild} and \textbf{SWC}, mentioned previously, implement
+minifiers in addition to their other capabilities and are faster than Terser.
Several CSS minifiers in common use today are cssnano, csso, and Lightning CSS.
-\href{https://cssnano.github.io/cssnano/}{\textbf{Cssnano}} and
-\href{https://github.com/css/csso}{\textbf{csso}} are pure CSS minifiers written in JavaScript and
-thus somewhat slow. \href{https://lightningcss.dev/}{\textbf{Lightning CSS}} is written in Rust and
-claims to be 100x faster than cssnano. Lightning CSS additionally supports CSS transformation and
-bundling.
+\href{https://cssnano.github.io/cssnano/}{\tb{Cssnano}} and
+\href{https://github.com/css/csso}{\tb{csso}} are pure CSS minifiers written in JavaScript and thus
+somewhat slow. \href{https://lightningcss.dev/}{\tb{Lightning CSS}} is written in Rust and claims to
+be 100x faster than cssnano. Lightning CSS additionally supports CSS transformation and bundling.
\section{Developer Tooling}
@@ -242,15 +244,15 @@ \subsection{Meta-Frameworks}
Meta-frameworks provide a curated set of already selected packages, including build tools, that
synergize and enable specialized application paradigms. For example,
-\href{https://nextjs.org}{\textbf{Next.js}} specializes in Server-Side Rendering (SSR) and
-\href{https://remix.run}{\textbf{Remix}} specializes in progressive enhancement.
+\href{https://nextjs.org}{\tb{Next.js}} specializes in Server-Side Rendering (SSR) and
+\href{https://remix.run}{\tb{Remix}} specializes in progressive enhancement.
Meta-frameworks typically provide a preconfigured build system, removing the need for you to stitch
one together. Their build systems have configurations for both production and development servers.
-Like meta-frameworks, build tools like \href{https://vitejs.dev/}{\textbf{Vite}} provide
-preconfigured build systems for both production and development. Unlike meta-frameworks, they do not
-force a specialized application paradigm. They are suitable for generic frontend applications.
+Like meta-frameworks, build tools like \href{https://vitejs.dev/}{\tb{Vite}} provide preconfigured
+build systems for both production and development. Unlike meta-frameworks, they do not force a
+specialized application paradigm. They are suitable for generic frontend applications.
\subsection{Sourcemaps}
@@ -264,14 +266,14 @@ \subsection{Sourcemaps}
publicizing the source code.
Each step of the build pipeline can emit a sourcemap. If multiple build tools are used to construct
-the pipeline, the sourcemaps will form a chain (e.g. \texttt{source.js} -> \texttt{transpiler.map}
--> \texttt{bundler.map} -> \texttt{minifier.map}). In order to identify the source code
-corresponding to the minified code, the chain of source maps must be traversed.
+the pipeline, the sourcemaps will form a chain (e.g. \tc{source.js} -> \texttt{transpiler.map} ->
+\tc{bundler.map} -> \texttt{minifier.map}). In order to identify the source code corresponding to
+the minified code, the chain of source maps must be traversed.
However, most tools are not capable of interpreting a chain of sourcemaps; they expect at most one
sourcemap per file in the distributable. The chain of sourcemaps must be flattened into a single
sourcemap. Preconfigured build systems will solve this problem (see Vite's
-\href{https://github.com/vitejs/vite/blob/feae09fdfab505e58950c915fe5d8dd103d5ffb9/packages/vite/src/node/utils.ts\#L831}{\texttt{combineSourcemaps}}
+\href{https://github.com/vitejs/vite/blob/feae09fdfab505e58950c915fe5d8dd103d5ffb9/packages/vite/src/node/utils.ts\#L831}{\tc{combineSourcemaps}}
function).
\subsection{Hot Reload}
@@ -315,11 +317,11 @@ \subsection{Monorepos}
Fortunately, there exist several monorepo tools designed specifically for frontend. Unfortunately,
they lack the flexibility and robustness of Bazel et al., most notably hermetic execution.
-The frontend-specific monorepo tools in common use today are \href{https://nx.dev/}{\textbf{Nx}} and
-\href{https://turbo.build/repo}{\textbf{Turborepo}}. Nx is more mature and featureful, while
-Turborepo is part of the Vercel ecosystem. In the past, \href{https://lerna.js.org/}{\textbf{Lerna}}
-was the standard tool for linking multiple JavaScript packages together and publishing them to NPM.
-In 2022, the Nx team took over Lerna, and Lerna now uses Nx under the hood to power builds.
+The frontend-specific monorepo tools in common use today are \href{https://nx.dev/}{\tb{Nx}} and
+\href{https://turbo.build/repo}{\tb{Turborepo}}. Nx is more mature and featureful, while Turborepo
+is part of the Vercel ecosystem. In the past, \href{https://lerna.js.org/}{\tb{Lerna}} was the
+standard tool for linking multiple JavaScript packages together and publishing them to NPM. In 2022,
+the Nx team took over Lerna, and Lerna now uses Nx under the hood to power builds.
\section{Trends}