Creating a Rollup Plugin to Copy and Watch a File
Tags: javascript, webdev, rollup
Published: 2020-09-18 07:33:01

---
title: Creating a Rollup Plugin to Copy and Watch a File
published: true
tags: javascript, webdev, rollup
# cover_image: ./cover-image/cover-image.png
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/kksxfr7yhch7ext7633l.png
---


A simple web application is what I needed to build. I was going for minimalistic approach: pure JavaScript and HTML, and only for modern browsers. Therefore, I didn't need any compiling/transpiling tools such as Babel. I did need, though, a tool that will bundle up my JavaScript files and a watcher to do that on every change. Rollup happened to be an excellent tool for my needs.


Rollup Configuration
--------------------

Standard way to set up the build process with Rollup is to write a Rollup configuration file `rollup.config.js`. Below is a simple config file on which I will base my approach. It instructs Rollup to bundle all JavaScript starting from input `main.js` into the destination folder `dist`.

`rollup.config.js`
```javascript
export default {
	input: 'main.js',
	output: {
		dir: 'dist',
		format: 'es'
	}
};
```

Now, one can run Rollup with:

```bash
rollup -c -w
```

Flag `-c` instructs Rollup to use the config file. Flag `-w` enables watch mode.


What About HTML?
----------------

I have my `index.html`. It is placed in the same folder as `main.js` and it contains this line:
```html
<script src="main.js" type="module"></script>
```
As such, it simply needs to be copied into the build destination folder, and recopied on changes in the watch mode. But Rollup deals only with JavaScript by default, it doesn't know how to handle HTML files. For this, we need to use Rollup plugins.

There are plenty of Rollup plugins that might be helpful in these situations. Some of them create their own `index.html` file based on some parameters. Some are designed to only copy specific assets. I have tried a few of them and was disappointed with the choices. Many come with a bunch of dependencies and other stuff that I don't need, and all I need it to simply copy a file and recopy it on every change. So, I decided to write my own plugin.


Writing a Rollup Plugin
-----------------------

A Rollup plugin is an object, with property `name`, and one or more functions that are called build hooks. This is an example of the structure with two hooks, `buildStart` and `generateBundle`.

```javascript
{
	name: 'my-rollup-plugin',
	async buildStart() => {
		// do some stuff
	},
	async generateBundle() => {
		// do some more stuff
	}
}
```

Hooks are functions which are called at various stages of the build. There are more than 20 build hooks currently defined in the [Rollup documentation](https://rollupjs.org/guide/en/#build-hooks). We will be using two hooks, namely, `buildStart` and `generateBundle`. As their names suggest, the former is called at the beginning of the build process, and the latter at the end right before writing the bundle to disk.

Rollup plugin is usually, if not always, wrapped in a function that returns an object with structure as above. That allows us to provide additional parameters. In our case we need to provide two parameters: input and output file names.

There are two additional functionalities that we need to complete this task. One is to tell Rollup to watch for changes in our file. Another is to save our file to the destination folder. Rollup provides so called [plugin context](https://rollupjs.org/guide/en/#plugin-context) utility functions to help us accomplish those tasks. Those functions are methods of the `this` object, and they are available in most hooks. We will be using utility function `this.addWatchFile` to set the file to be watched in watch mode, and `this.emitFile` to define a file that needs to be included in the build output.

Without further ado, here is the plugin that copies a file to the build output folder, and watches for changes:

```javascript
function copyAndWatch(fileIn, fileOut) {
	return {
		name: 'copy-and-watch',
		async buildStart() {
			this.addWatchFile(fileIn);
		},
		async generateBundle() {
			this.emitFile({
				type: 'asset',
				fileName: fileOut,
				source: fs.readFileSync(fileIn)
			});
		}
	}
}
```

One can notice that I am using `fs`. This is a node module that enables interacting with the file system, and it needs to be imported.

Usually, I would save a plugin in a separate file. But here, for simplicity, I am defining it in `rollup.config.js`, since it is a very short function, and after all, it's been used only for the rollup configuration. 

Finaly, here is how my complete rollup configuration file looks like:

`rollup.config.js`
```javascript
import fs from 'fs';

export default {
	input: 'main.js',
	output: {
		dir: 'dist',
		format: 'es'
	},
	plugins: [
		copyAndWatch('index.html', 'index.html')
	]
};

function copyAndWatch(fileIn, fileOut) {
	return {
		name: 'copy-and-watch',
		async buildStart() {
			this.addWatchFile(fileIn);
		},
		async generateBundle() {
			this.emitFile({
				type: 'asset',
				fileName: fileOut,
				source: fs.readFileSync(fileIn)
			});
		}
	}
}
```


Two Final Points
----------------

1) There are many build hooks that can be used for writing a plugin like this one. Although, many would do the job, it is tricky to select just the right one for the job without knowing their particular meaning in the build process. Rollup documentation can be daunting in this regard. It is easy to find similar plugins on the internet and very often they use the `load` hook for assigning a file to the watcher. I would argue that the `load` hook is a poor choice in this situation, as this hook is being called for every imported file. Instead, we'd rather add our file to the watcher only once at the beginning of the build process.

2) I have used the `fs` module for reading a file from disk. I could have used the same module for writing to disk as well. In fact, many solutions exist with this approach, and they work just fine. But using Rollup's utility function `this.emitFile` is more suitable as it gives the control of writing the files back to Rollup to finish the build process in its own way.