Rollup's intro and outro - Two Amazing Features You Can't Miss!
Tags: javascript, rollup, webdev, tutorial
Published: 2024-02-05 06:00:00

## Table of contents
- [What is Rollup?](#What_is_Rollup)
- [The very basic setup](#The_very_basic_setup)
- [The `intro` and `outro` properties](#The_intro_and_outro_properties)
- [An `intro`/`outro` example](#An_intro/outro_example)
- [A more practical example](#A_more_practical_example)

## What is Rollup?<a name="What_is_Rollup"></a><a name="What_is_Rollup"></a>

If you're not aware of it, [Rollup](https://rollupjs.org/) is a JavaScript module bundler.

To further elaborate upon the latter, a JavaScript module bundler is a program that takes in a bunch of [JavaScript modules](https://www.codeguage.com/courses/advanced-js/modules-basics) (usually via one single entry point file) and then bundles them all together into one single file.

In the frontend world, bundling is more than just common — it's more or less a necessity given the fact that it allows us to deliver just one single file to the client along with numerous optimizations performed on that file, such as code splitting and [minification](https://www.codeguage.com/blog/what-is-minification).

While the de-facto out there for bundling JavaScript files would probably be the ultimate beast [Webpack](https://webpack.js.org/), by no means is it the _only_ bundler. For simple projects, Webpack might be quite cumbersome to set up. As a matter of fact, there are complete books written on just configuring and navigating Webpack, so you can just imagine the level of complexity and power it comes equipped with. It's all good but _too much_ for basic needs.

Rollup is the perfect choice to use in simpler cases. But don't get us wrong; we don't mean to say that Rollup is a simple module bundler program. A big NO!

Rollup is quite mature, powerful, and capable in handling the bundling concerns of a complex project in numerous ways. There is a host full of configurations that can be done in order to tweak it according to our requirements.

In this article, we aim to explore just that.

In particular, following we get to see two configurative properties of Rollup — **`intro`** and **`outro`** — which are obscurely hidden down within the advanced section of the documentation but that would be very useful in certain cases.

If you're an avid Rollup user, these configurations might even get you to say: _"Oh yeah, I was looking for just that!"_

Let's begin the exploration. _Let's roll up!_

-----

## The very basic setup<a name="The_very_basic_setup"></a><a name="The_very_basic_setup"></a>

Before we dive into exploring `intro` and `outro`, let's quickly spare 5 minutes or even less in getting a quick recap of how to set up Rollup to be able to bundle a project directory.

### The `rollup` command<a name=""></a>

First off, we need to make sure that the `rollup` command is available for us to call within the project's directory.

The best way to do so, given that the directory is an npm project with a `package.json` file, is to use an npm script. A good name for the script could be `build`.

Here's how the `package.json` would look with this npm script:

```json
{
  ...
  "scripts": {
    "build": "rollup -c -w"
  },
  ...
  "dependencies": {
    "rollup": "^4.9.6"
  }
}

```

### The `rollup.config.mjs` file<a name=""></a>

Rollup could be provided a lot of configurations right when we call it in the terminal. However, due to the verbosity of the terminal, there's a high chance that this is used sparingly.

Instead, the most common approach of configuring Rollup is to use a `rollup.config.mjs` file.

**💡 Notice**: The `.mjs` extension here is used in order to instruct the Node.js runtime that runs the rollup program to treat the configuration file as an [ECMAScript module](https://www.codeguage.com/courses/advanced-js/modules-basics). Without this, the `package.json` file would need `type:"module"` in order to not squawk an error. Otherwise, the runtime would throw an error as soon as it encounters the `import` keyword.

Here's the general structure of the file:

```js
export default {
   input: inputFilePath,
   output: {
      file: outputFilePath,
      format: format
   },
   plugins: plugins
}
```

A default-exported object defines all of the settings for the current build process of Rollup. Three typical properties are as follows:

- `input`: The path to the input file
- `output`: An object containing the necessary details for the bundled, output file.
- `plugins`: An array containing a list of plugins to apply to the bundling.


For example, let's say we have the following directory structure of a project:

![A simple project using Rollup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vl3o93875dwgx19740rm.png)

A simple project using Rollup



All the scripts reside in the `Scripts` directory while all the static files (including images and CSS files) reside in the `static/scripts` directory.

(Another common structure might be the `public` and `src` directories, with `public` being served over HTTP for the public and `src` kept for the development team only.)

The `index.js` file in `Scripts` is the entry point of the project that calls upon all the rest of the files in `Script`. And this `index.js` file is exactly the file that Rollup is concerned with.

Here's how we'd set up Rollup to bundle `index.js` (and the entire tree of files that are imported into it) into a `bundle.js` file inside `static/scripts`:

```js
export default {
   input: 'Scripts/index.js',
   output: {
      file: 'static/scripts/bundle.js',
      format: 'iife'
   },
}
```

Now, as you can see, this example doesn't utilize any of the potent plugin power of Rollup. However, usually projects using Rollup do. Let's do that up next.

### Plugins<a name=""></a>

The great thing about Rollup is that it is easily extendible via external plugins that can be used to modify how the bundling process goes and what happens therein.

Let's use the `terse` plugin in our example. `terse` is used to make a bundle terse, i.e. compact. It minifies all the code in the bundle and saves on a significant number of bytes.

Here's the code with the plugin:

```js
import terse from '@rollup/plugins-terse';

export default {
   input: 'Scripts/index.js',
   output: {
      file: 'static/scripts/bundle.js',
      format: 'iife'
   },
   plugins: [
      terse()
   ]
}
```

Now, in order to perform the bundling, we just ought to call `npm run build`:

Output:
```
> npm run build
```

And that's essentially it for the quick review of Rollup.

It's now time to get to the very crux of this article — Rollup's **`intro`** and **`outro`** output options.

-----

## The `intro` and `outro` properties<a name="The_intro_and_outro_properties"></a><a name="The_intro_and_outro_properties"></a>

The optional **`intro`** and **`outro`** properties go inside the `output` object that's part of the default export of Rollup's configuration file. They specify additional code to prepend or append to the wrapper of a code bundle.

Let's see how they both work.

**`intro`** specifies a piece of text to add right at the **beginning** of the entire bundled code **before** it is converted to the desired output format.

On the same lines:

**`outro`** specifies a piece of text to add right at the **end** of the entire bundled code **before** it is converted to the desired output format.

(By the way, _'outro'_ is actually a word in English. It means: _"a closing section."_ _Good nomenclature for the property, isn't it?_)

It's extremely important to note an important point in both these definitions. That is, `intro` and `outro` get applied before the code is converted into a given format by Rollup.

In contrast, the `banner` and `footer` properties (which may seem identical to `intro` and `outro`) get applied after the code is converted into a given format by Rollup.

It's easier seen than said, so if you're confused by this distinction, don't worry, for we'll demonstrate `intro` and `outro` up next using a concrete example.

-----

## An `intro`/`outro` example<a name="An_intro/outro_example"></a><a name="An_intro/outro_example"></a>

Going with the same setup as shown above (with `Scripts` and `static/scripts`), suppose that the `index.js` file has the following code in it:

```js
function add(a, b) {
   return a + b;
}

console.log(add(2, 3));
```

A simple function adding two numbers together followed by a console log statement.

If we bundle this file (into `bundle.js` inside `static/scripts`), here's the code we get (without the `terse` plugin):

```js
(function () {
   'use strict';

   function add(a, b) {
      return a + b;
   }

   console.log(add(2, 3));

})();

```

Notice that the code is wrapped inside an IIFE and that's because we specified so in the `rollup.config.mjs` file.

**Further reading:**
**_IIFE_** stands for **_Immediately Invoked Function Expression_**. If you wish to explore more details about an IIFE in JavaScript and why it's often used to wrap code, consider reading [JavaScript Functions - Basics: IIFEs](https://www.codeguage.com/courses/js/functions-basics#IIFEs).

If we wish to add some code before and after the code wrapped up inside the IIFE, we seek the `intro` and `outro` properties. In other words, `intro` and `outro` go **inside** the IIFE (or any other format-specific wrapper function).

This is exactly what Rollup's official documentation for [`intro`/`outro`](https://rollupjs.org/configuration-options/#output-intro-output-outro) states:

[They are] similar to output.banner/output.footer, except that the code goes inside any format-specific wrapper.Let's say we want to add a console log before the code begins executing and after it all finishes executing. In this case, we can leverage `intro` and `outro` as shown below:

```js
export default {
   input: 'Scripts/index.js',
   output: {
      file: 'static/scripts/bundle.js',
      format: 'iife',
      intro: "console.log('Starting');",
      outro: "console.log('Ending');"
   }
}
```

When the bundling completes, here's what the bundled file contains:

```js
(function () {
   'use strict';

   console.log('Starting');

   function add(a, b) {
      return a + b;
   }

   console.log(add(2, 3));;

   console.log('Ending')

})();

```

Notice how the `intro` text comes at the start _inside_ the IIFE while the `outro` text comes at the end, again inside the IIFE.

And this is essentially everything about `intro` and `outro`.

At this point you might be wondering _"Yeah well, `intro` and `outro` are good, but what's so exciting about them?"_

Well, sometimes it's not the excitement around a given utility but rather around the problem that it can solve. And one such problem that can be solved easily using `intro`/`outro` while working with Rollup is discussed next.

-----

## A more practical example<a name="A_more_practical_example"></a><a name="A_more_practical_example"></a>

In browser's, to keep from delaying the occurrence of the very important `DOMContentLoaded` event and `load` as well, developers usually defer the execution of certain pieces of code until one or the other happens.

For instance, if we have a CSS stylesheet to import a font — the ones that are typically used to import the cool stuff from [Google Fonts](https://fonts.google.com/) — we could do the following:

```html
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,500;0,700&display=swap">
```

There's nothing wrong with this per se. However, on a high-latency network, it will keep the page loading and loading until the CSS file and its imported fonts aren't completely downloaded. This can be problematic.

As web technology has evolved, so has the expectation of every user using it. Now, users don't expect websites to stay unresponsive for more than a handful of seconds (_yes that's right — seconds!!_). In this regard, optimizing websites to make sure that they load faster than the blink of an eye, or at least close to it, is a paramount craft to exercise.

There are numerous things we could do to [improve the loading times of a webpage](https://www.codeguage.com/blog/7-ways-decrease-load-times). One of them is to render fonts as soon as the document is loaded completely, i.e. upon the occurrence of its `load` event.

In order to render the stylesheet shown above in this way, here's the JavaScript code we'll need:

```js
function loadStylesheet(href) {
   var linkElement = document.createElement('link');
   linkElement.rel = 'stylesheet';
   linkElement.href = href;
   document.head.appendChild(linkElement);
}

window.addEventListener('load', function() {
   loadStylesheet('https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,500;0,700&display=swap');
});
```

It's fairly simple enough to understand.

Sometimes, while developing complex apps, it won't just be external font stylesheet imports that ought to be put inside a `load` handler; we might have whole scripts waiting to get the same treatment. Now this comes with an issue.

_How can we bundle such programs?_

Let's consider an example to help clarify this.

Suppose we have two different JavaScript files, one loading a couple of external fonts and one loading a couple of images, both upon the occurrence of the `load` event:

fonts.js
```js
window.addEventListener('load', function() {
   function loadStylesheet(href) {
      var linkElement = document.createElement('link');
      linkElement.rel = 'stylesheet';
      linkElement.href = href;
      document.head.appendChild(linkElement);
   }

   // Load a couple of fonts.
   loadStylesheet('https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,500;0,700&display=swap');
   loadStylesheet('https://fonts.googleapis.com/css2?family=Commissioner:ital,wght@0,500;0,700&display=swap');
});
```

images.js
```js
window.addEventListener('load', function() {
   document.querySelectorAll('.lazy-img').forEach(function(lazyImageElement) {
      // Load the image by assigning it a src attribute.
      lazyImageElement.src = lazyImageElement.getAttribute('data-src');
   });
});
```

Now we wish to bundle both these files together.

Clearly, there's nothing difficult about that. We'd have the following in `index.js`:

index.js
```js
import './fonts';
import './images';
```

Upon bundling, here's what we'd get:

bundle.js
```js
(function () {
   'use strict';

   window.addEventListener('load', function() {
      function loadStylesheet(href) {
         var linkElement = document.createElement('link');
         linkElement.rel = 'stylesheet';
         linkElement.href = href;
         document.head.appendChild(linkElement);
      }

      // Load a couple of fonts.
      loadStylesheet('https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,500;0,700&display=swap');
      loadStylesheet('https://fonts.googleapis.com/css2?family=Commissioner:ital,wght@0,500;0,700&display=swap');
   });

   window.addEventListener('load', function() {
      document.querySelectorAll('.lazy-img').forEach(function(lazyImageElement) {
         // Load the image by assigning it a src attribute.
         lazyImageElement.src = lazyImageElement.getAttribute('data-src');
      });
   });

})();

```

However, if we suppose that there are 10 - 20 such files, we might start to question our decision of wrapping up _every_ single file's main logic with a `load` event handler.

_Is that the right thing to do?_ Well, probably or probably not.

If the answer is the latter, _how could we improve this in Rollup?_

The answer is to use `intro`/`outro`.

If you notice the bundled code above, the entire part before and including the `load` handler's left brace (`{`) could be made the `intro` while the entire part after and including the handler's right brace (`}`) could be made the `outro`.

In this way, we could let go off the handler from both the files and have their code written in the top level individually, while the bundled code generated by Rollup automatically gets `intro` and `outro` injected before and after it, respectively, so to have the entire code inside one single `load` handler.

_Simple._

In order to achieve this, we can either manually extract the part before and including the left brace (`{`) and assign it to `intro`, and do the same for `outro`. Or, we could be slightly lazier — in a programmatic way — and get this to be done by code itself.

We are quite lazy (in a good way), and so we'll go with the latter.

We start by creating a template JavaScript file that defines the code where our bundled code ought be injected:

template.js
```js
window.addEventListener('load', function() {
   /* ... */
});
```

In this file, the comment `/* ... */` holds a special meaning — it's where the bundled code will get injected by Rollup.

_So far, so good._

Now, let's come to `rollup.config.mjs` and extract the text of `intro` and `outro` from this template file. We'll use Node's `readFileSync()` function for this task:

```js
import { readFileSync } from 'fs';
 
const PLACEHOLDER = '/* ... */';

const fileStr = readFileSync('./template.js').toString();
const index = fileStr.indexOf(PLACEHOLDER);
const intro = fileStr.slice(0, index);
const outro = fileStr.slice(index + PLACEHOLDER.length);

export default {
   input: 'Scripts/index.js',
   output: {
      file: 'static/scripts/bundle.js',
      format: 'iife',
      intro,
      outro
   }
}
```

Both the files, `fonts.js` and `images.js` will get modified as well:

fonts.js
```js
function loadStylesheet(href) {
   var linkElement = document.createElement('link');
   linkElement.rel = 'stylesheet';
   linkElement.href = href;
   document.head.appendChild(linkElement);
}

// Load a couple of fonts.
loadStylesheet('https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,500;0,700&display=swap');
loadStylesheet('https://fonts.googleapis.com/css2?family=Commissioner:ital,wght@0,500;0,700&display=swap');
```

images.js
```js
document.querySelectorAll('.lazy-img').forEach(function(lazyImageElement) {
   // Load the image by assigning it a src attribute.
   lazyImageElement.src = lazyImageElement.getAttribute('data-src');
});
```

See how we've thrown away the `load` handler from both of these files, having put all our trust in Rollup's `intro` and `outro`.

With the code written, let's see how the bundling goes.

This time, when we bundle the application code, here's what we get:

bundle.js
```js
(function () {
   'use strict';

   window.addEventListener('load', function() {


   function loadStylesheet(href) {
      var linkElement = document.createElement('link');
      linkElement.rel = 'stylesheet';
      linkElement.href = href;
      document.head.appendChild(linkElement);
   }

   // Load a couple of fonts.
   loadStylesheet('https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,500;0,700&display=swap');
   loadStylesheet('https://fonts.googleapis.com/css2?family=Commissioner:ital,wght@0,500;0,700&display=swap');

   document.querySelectorAll('.lazy-img').forEach(function(lazyImageElement) {
      // Load the image by assigning it a src attribute.
      lazyImageElement.src = lazyImageElement.getAttribute('data-src');
   });


   });

})();

```

_See?_ Both the code files get amalgamated into one single block of code, wrapped up inside just one `load` handler, all thanks to `intro` and `outro`.

-----