Skip to content

Commit

Permalink
some tweaks; incorporate my personal abbreviation system to the latex
Browse files Browse the repository at this point in the history
  • Loading branch information
azuline committed Jun 17, 2024
1 parent b165dbf commit 633cb72
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 64 deletions.
3 changes: 2 additions & 1 deletion src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
<div>sunset glow / under construction :)</div>
<div>
<a href="mailto:[email protected]">[email protected]</a> /
<a href="https://github.com/azuline">azuline@github</a>
<a href="https://github.com/azuline">azuline@github</a> /
<a href="https://x.com/hiddenwaterways">hiddenwaterways@twitter</a>
</div>
<div>posts:</div>
<div><a href="/posts/frontend-build-systems.html">Exposition of Frontend Build Systems</a></div>
Expand Down
128 changes: 65 additions & 63 deletions src/posts/frontend-build-systems.tex
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
\documentclass{article}
\usepackage{enumitem}
\newcommand{\ti}{\textit}
\newcommand{\tb}{\textbf}
\newcommand{\tc}{\texttt}
\title{Frontend Build Systems}
\begin{document}

Expand All @@ -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}

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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}
Expand All @@ -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
Expand All @@ -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}

Expand All @@ -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}

Expand All @@ -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}
Expand Down Expand Up @@ -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}

Expand Down

0 comments on commit 633cb72

Please sign in to comment.