One of the things I found very confusing when I first learned how to program was understanding when a certain piece of code runs. Of course, roughly speaking the code runs when you run the program. This timespan is called runtime. However, if the program contains asynchronous code runtime does not necessarily mean executing code which means there is another layer of timespans to consider. Depending on the language and setup we might also have a compile or build step and end up with another timespan called compile time. And to make everything really confusing, we can even write code that modifies our code at compile time such that compile time of one part is now runtime of another part of our code.

I recently discovered that it is actually not that confusing and want to present a minimal example in JavaScript illustrating all the different concepts. If you have ever been confused by the question *“when does this code actually run?” *as I have been, I hope this will help you.

The code, tools and setup

In order to clarify the concepts, the example code has to be as simple as possible. If you want to run it yourself, you can find it on GitHub1. However, it is just a few lines of JavaScript that are compiled with Babel using a plugin called babel-plugin-preval2. The program in source.js goes as follows:

// source.js
import preval from 'babel-plugin-preval/macro'

setTimeout(() => console.log(preval`
	const fs = require('fs')
	module.exports = fs.readFileSync(__dirname + '/message.txt', 'utf8')
`), 1000)

If this looks super weird to you, that is totally fine. We will go through it step by step. After compiling it with npm run build we get the compiled version that Babel generated in build.js:

// build.js
setTimeout(() => console.log("Done.\n"), 1000);

Running this code with Node will do nothing for one second, then print the message to the screen and terminate. The steps we took in order to achieve this are:

  1. Write source code
  2. Compile source code
  3. Run compiled code

Now let’s map the different parts of the code to the different time spans.

Compile time

At compile time we run Babel with babel-plugin-preval as its only transformation to perform. Babel is a JavaScript source to source compiler which simply means JavaScript in, JavaScript out. The preval plugin basically allows you to execute parts of your code at compile time. In our example, it executes the following code at compile time:

const fs = require('fs')
module.exports = fs.readFileSync(__dirname + '/message.txt', 'utf8')

This puts the content of the file message.txt directly into the compiled code. This works as long as there is such a text file in the directory where the code is compiled. For more detailed information about the hows, whats and whys of babel-plugin-preval have a look at its repository.

Therefore, one part of our source code actually executed at compile time! Although Babel and this plugin might be young projects, the basic concept is very old and present in many different programming languages. The C preprocessor can do similar things for example. There are important differences, but I just want to give a rough analogy. The important bit to understand is that the code above is part of our source code, but runs at compile time and is not part of our compiled code itself.

Runtime and Execution

Runtime is the time span from invoking the compiled program with Node to its termination. This should be about one second for our example code. However, most of this time none of our code is executed. The setTimeout call is synchronous and will run and terminate upon program invocation. Then none of our code is executed for one second until the callback we gave to setTimeout is called. The complete process behind this is quite involved and asynchronous programming is a topic for itself.

In summary, our source code runs scattered over three different time spans. I admit that the example code is artificial, but the point is to have it as minimal as possible. The concept of the different time spans are not difficult, but can get blurred in real application code because there is a lot of other things to keep in your head. Distilling it into as few lines as possible really helped me and hopefully someone else too!