Improving our program
In the previous chapter, we wrote our first hlb
program to install the dependencies of a node project. It currently looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
If we change to a bigger project, copying over the node_modules
to a scratch filesystem only to isolate the directory is pretty expensive. In this chapter, we'll learn about techniques to improve our build which isn't available through the Dockerfile frontend.
Removing the unnecessary copy¶
Instructions like image
, run
, and copy
are also regular functions, and you must invoke them with the required arguments. But some builtin functions have optional features that can be accessed in an option block. For example, run
has an optional function mount
that temporarily mounts a filesystem to a mountpoint
directory while run
is executing.
The builtin mount
function has the following signature:
1 2 |
|
We can apply options to the run
function by adding with <option>
after the arguments, where option
can be a function or a block literal. Let's first take a look at block literals.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Block literals cannot be defined in the global scope, but you can define them where an argument is expected. When the statements within a block are all in one line, each statement must be suffixed with a ;
as a delimiter. In the final example above, we defined an option block literal that mounts a scratch filesystem into /output
, ready to receive output files. Handy!
Now with this new ability, let's avoid the unnecessary copy
!
1 2 3 4 5 6 7 8 9 |
|
This time, when npm install
runs, it will write the files directly into the scratch filesystem we mounted to /src/node_modules
. Hooray!
But wait, if we run the build targeting npmInstall
, that will still give us the alpine filesystem. Not only that, once npm install
has finished, the scratch filesystem will be unmounted leaving behind no node_modules
directory at all! Haven't we made things worse?
Fear not, for there is still one last concept to introduce!
Defining aliases¶
Currently, the only targets we can run are functions we defined in the global scope. We can also target statements inside the body of a function by defining an alias.
After the arguments to the function and the optional with <option>
block, you can add as <identifier>
to define an alias for the filesystem at that step. Usually options are not allowed to be aliased but mount
is an exception.
1 2 3 4 5 6 7 8 9 |
|
Try running a build targetting nodeModules
now, and this time we don't have to download it.
hlb run --target nodeModules node.hlb
Your build should complete slightly quicker (or much quicker if you had more dependencies), but we don't have to stop there. We briefly mentioned in the previous chapter that we chose to start from a filesystem of a Docker image, so we can explore other source functions too.
Git sources¶
Before npm install
happens, we need to clone the repository, and before that happens we need to download the node:alpine
image. However, cloning the repository doesn't strictly depend on the node:alpine
image. What if we could pull the image and clone the repository concurrently?
Using the mount
function we just learnt, we can define a new fs
function that clones the repository and mount it. But that will still require another image. Luckily for us there is a git
builtin function that can efficiently prepare a filesystem containing a git
repository! Here's the signature:
1 2 3 |
|
Instead of cloning the repository and then running it, we can implicitly depend on a function that checkouts our repository.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Run the build again to see the speed improve once again.
hlb run --target npmInstall node.hlb
Formatting
Consistency helps with readability, so hlb
comes with a formatter so that programs will look consistently formatted. Simply run hlb format -w node.hlb
and it will format your file for you.
Recap¶
At the end of two chapters, we have wrote our first hlb
program and optimized it by writing node_modules
into a mount. Then we improved it a little more by leveraging a git
source, allowing the clone to happen concurrently with the pull of the node:alpine
image.
You may have noticed that all this time, all our functions we defined had no arguments. In the next chapter, we'll refactor our example to make our program more generic.