Last month, I was struggling to give good answers to questions like "Why can't I just plug a .scss
file into a link element's href
and have that work in development?", or "Why can't I reference a .css
file from a neighbouring project's node_modules
within a static file?"
A crucial reason for this is due to the way JS frameworks rely on multiple transpiling and bundling processes.
The rapid visual feedback of hot module reloading (HMR) in local development environments can make us forget that the outgoing application code doesn't treat files the same way!
Runtime vs. Build Time
We can't discuss what a "build" means in a Javascript application without mentioning:
- runtime
- build time
- parsing and execution time
Note: I left out compile time since JS is an interpreted language
Runtime
Runtime happens when the software is executing. This can be when an application makes requests and the server responds–maybe after a server-side computed process.
Runtime also refers to the actual tool running the code: like Node, or Deno.*
The idea is further confused by the fact we use node
or npm
in our commands to run the runtime in local dev environments. 😂
TLDR: npm
and yarn
are package managers.
Node and Deno are runtimes that create an environment for the code you write to be executed, built, even deployed.
As you're developing, runtimes allow you to access backends, enter environment variables--all in the same place without the need for a browser.
*If you wanted to split hairs, the browser is arguably a runtime environment for Javascript files.
Build Time
Build time consists of the following processes:
Compiling/Transpiling
Jonas Bondi. Angular vs. React: Compilers, Medium, 2017.
When you write modern Javascript apps, you usually need to outfit your project with a transpiler like Babel or Typescript to compile the Javascript to ES5 or CommonJS, a form of Javascript that Node was initially introduced in, and all browsers could parse.
In the case of CSS preprocessors like Sass, it is compiling to CSS. With PostCSS, unique classes might be generated and treeshaken.
Minifying
Merging all code into a single file and removing all excessive spacing such that it is compact, and bundling and runs faster.
Codesplitting
Chunking and separating code to relevant .js
files -- such that you're able to import just the files pertinent to a button component, instead of a whole library.
When you run npm run build
, all processes mentioned in above are started to prepare a production ready bundle of Javascript, CSS and HTML (often with source maps) that will be ready for production deployment.
Development mode
During development mode, the build tool Webpack's Hot Module Replacement (HMR) feature allows applications to conveniently rebuild at runtime.
In most cases a local cache folder of application code is built, with subsequent changes constantly rebuilt to give developers fast-feedback of end-users' experience as you're developing... but all those fresh buildz don't appear in your output (dist/
) folder.
Today there are so many fancy features that optimize the dev experience of being able to preview the code you're writing in JS frameworks (cough Ahead-Of-Time compiling in Angular, incremental builds in Gatsby and for Next, Incremental Static Regeneration uses such caches both in dev and on the server to decrease the rebuild times for its statically generated views.)
With the rise of a newer generation of build tools and bundlers written in Rust and Go, build times are drastically lowering.
Execution Time
Execution happens in the browser. In single page applications, the minified files returned from the server go through what happens after you type the URL into the browser.
"V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++. It is used in Chrome and in Node.js..." v8.dev
Fig 2.1 The lifecycle of a client-side web application starts with the user specifying a website address (or clicking a link) and ends when the user leaves the web page. It’s composed of two steps: page building and event handling.
"Secrets of the Javascript Ninja, Second Edition" by John Resig, Bear Bibault, Josip Maras, August 2016.
In the web 1.0 times, a DOM tree would be created by parsing of html files. These days, JS functions (like React.createElement()
) create and append those nodes.
On initial load, JS is read (parsed) top to bottom once, and the relevant execution contexts are set for every single function invocation in a first-on-first-off manner. As the session continues, functions are invoked asynchronously in a last-in-first-out manner.
Sukhjinder Arora,"Understanding Execution Context and Execution Stack in Javascript", Medium, 2018.
Aside from the load time, scripting time constitutes the registering of event listeners and dynamic content during the session.
A lot of foundational resources cover Javascript execution, but rarely do we connect all types of "time" to see where they fit together!
Which build tools are you excited for in 2023?
What are concepts that you found hard to place in your programming career?
Let me know in the comments.
Thanks to @naismith Shreya Dahal, Chang, Jimmy Jansen, Matt Mackay for helping me clarify my mental models!
Related Reading
Dan Abramov. "How does the development mode work?", overreacted. August 4, 2019.
Hugh Haworth. "Comparing the New Generation of Build Tools", css-tricks, Apr 8, 2021 (Updated on Jan 5, 2022)
"State of JS 2021: Build Tools", State of JS. 2022.
Mariko Kosaka. "Inside look at modern web browser (part 3)" Chrome Developers, August 18, 2020.
Addy Osmani. "Javascript Start-up Performance", Feb 9, 2017.
Top comments (0)