/assets/rollup.png

How to write a simple rollup or vite plugin

Motivation

Writing a vite or rollup plugin in itself isn’t that difficult, but for those of us who haven’t done so, it can be very challenging to do so.

The one thing that somehow scared me away from writing my own was, that you had to install them using npm, keep them public and update them for everyone else to use. At least that’s what I felt like.

Now I know that I can happily write my own plugins, without being scared about its complexity and implementations. Writing ones own plugins is very useful and you learn a lot about how your project works.

In this post I will show you a simple, but useful self written rollup plugin, which I can use in a vite project. In this case I will be using the awesome sveltekit.

Project Setup

What do we need to write a plugin for a project? That’s right, a project. So we initialize it:

bash
npm init svelte@next my-app

Now we have the project my-app. We go in there and start looking for our config file. It this case this will be our svelte.config.js. In this tutorial we will create a plugin that creates a file called build.txt as soon as we build our project. This will help us reproduce our (maybe) buggy environment locally if we have more than one.

Here we have a property called vite with a property plugins. This is where we will reference our metadata plugin:

js
import metadata from './rollup-plugins/metadata.js'
const config = {
kit: {
vite: {
plugins: [
metadata({'important': 'data'}),
],
},
},
}

I like to create my functions and methods of any kind depending on how I want to use them. That’s why I start like this, instead of directly writing my plugin. So now that we know how we want to use our plugin we can create it and start writing

bash
mkdir rollup-plugins
touch rollup-plugins/metadata.js

You have to know, that rollup and vite plugins are nothing more functions that return objects. It can even be the object itself. No need to call it. So we start

js
export default function metadata(rest) {
return {
// this name will show up in warnings and errors
name: 'rollup-plugin-metadata',
writeBundle: (options, bundle) => {
// do something
}
}
}

Here we have a simple function that receives a parameter called rest which can be any JavaScript object. This way we’re more flexible, which is good. The name can be any string you can come up with. The way I wrote it is simply convention for rollup plugins. This name will be what you see if your pluging throws an error.

The writeBundle thingy is called a build hook. We don’t need to write anything fancy, so here we can look up the rollup documentation. This hook allows us to read the bundle and the output directory. Exactly what we need for our metadata file.

Now it’s time to think about what we want to write into our build.txt file at the end of the build. I usually go with

  • buildHash - the hashed code to identify the code
  • commitHash - the hash of our git commit
  • date - well, that’s the date you created the build We want to hash the build our self, so we need a function to do so:
js
function hashCode(string) {
let hash = 0;
if (string.length == 0) {
return hash
}
for (let i = 0; i < string.length; i++) {
const char = string.charCodeAt(i)
hash = ((hash<<5)-hash)+char
hash = hash & hash // Convert to 32bit integer
}
return hash
}

With this function we can create our build hash which changes our plugin to

js
export default function metadata(rest) {
function hashCode(string) {
let hash = 0
if (string.length == 0) {
return hash
}
for (let i = 0; i < string.length; i++) {
const char = string.charCodeAt(i)
hash = ((hash<<5)-hash)+char
hash = hash & hash // Convert to 32bit integer
}
return hash
}
return {
// this name will show up in warnings and errors
name: 'rollup-plugin-metadata',
writeBundle: (options, bundle) => {
const metadata = {
buildHash: Math.abs(hashCode(JSON.stringify(bundle))),
}
}
}
}

Now we have an object with a number hash of the build. For the commitHash we have to use some nodejs magic and import child_process. With this we can simply add the following code to out metadata object and we’re ready to go

js
commitHash: childProcess.execSync('git rev-parse --short HEAD').toString().replace('\n', ''),

As you can see a child process enables us to use the command line. So we can use git to get our hash. For the last two, the date and the rest parameter, it’s very simple:

js
date: new Date(),
...rest,

Date is the JavaScript date object and we can simply spread our rest parameter at the end of the object. But be careful. Anything you put into the rest parameter that has the same key as an existing value will overwrite the later. All there is left to do is create and save the file. To do so we can also use the child process and bash’ echo function

js
childProcess.execSync(`echo ${JSON.stringify(metadata)} > ${options.dir}/build.txt`)

If we combine all of the steps we should have a file that looks something like the following:

js
import childProcess from 'child_process'
export default function metadata(rest) {
function hashCode(string) {
let hash = 0
if (string.length == 0) {
return hash
}
for (let i = 0; i < string.length; i++) {
const char = string.charCodeAt(i)
hash = ((hash<<5)-hash)+char
hash = hash & hash // Convert to 32bit integer
}
return hash
}
return {
// this name will show up in warnings and errors
name: 'rollup-plugin-metadata',
writeBundle: (options, bundle) => {
const metadata = {
buildHash: Math.abs(hashCode(JSON.stringify(bundle))),
commitHash: childProcess.execSync('git rev-parse --short HEAD').toString().replace('\n', ''),
date: new Date(),
...rest,
}
// save metadata as file
childProcess.execSync(`echo ${JSON.stringify(metadata)} > ${options.dir}/build.txt`)
}
}
}

With this in place you can call npm run build which will create a file called build.txt in your build directory. It’s content should look like this

json
{"buildHash":2021946242,"commitHash":"57b99c1","date":"2021-05-27T19:08:36.447Z","important":"data"}

Wrap Up

That’s all there is to it to our custom plugin. No uploading or installing, it can be as project specific as you want. Nothing to fear. I hope this little plugin helps you in your future projects. It sure helps me a lot to debug, in cases I find it difficult to reproduce an error.