DEV Community

Cover image for Creating a Rollup Plugin to Copy and Watch a File
Luka
Luka

Posted on

Creating a Rollup Plugin to Copy and Watch a File

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

export default {
    input: 'main.js',
    output: {
        dir: 'dist',
        format: 'es'
    }
};
Enter fullscreen mode Exit fullscreen mode

Now, one can run Rollup with:

rollup -c -w
Enter fullscreen mode Exit fullscreen mode

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:

<script src="main.js" type="module"></script>
Enter fullscreen mode Exit fullscreen mode

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.

{
    name: 'my-rollup-plugin',
    async buildStart() => {
        // do some stuff
    },
    async generateBundle() => {
        // do some more stuff
    }
}
Enter fullscreen mode Exit fullscreen mode

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. 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 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:

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)
            });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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)
            });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)