How To Generate Contents For Posts With JavaScript?

The Problem

Sometimes we write longer articles, posts or documents with titles and subtitles. Having many titles/subtitles it would be nice if we could easily generate a "contents" section at the top of the article. I am using ghost for my blog and my theme, unfortunately, is missing this feature. So I decided to write a JS library that I can easily add to my blog and generate contents for chosen posts.

The Solution

JavaScript Solution

First I wrote the solution in an example HTML and JS file. I wanted a simple unordered list generated based on the headings. I wanted to specify the source element selector i.e. from where should the library parse the headings and the target element selector i.e. where should the contents section be generated. The solution needed some recursion to generate a hierarchical structure based a flat list of headings.

Unit Test

To test my solution and to be confident about changes in the future we need, of course, some tests. I used Jest and the jest-environment-jsdom plugin for this.

Transpilation

Unfortunately, writing a plain JavaScript solution, TypeScript in this case, is not enough. We also need to make it easily usable by other apps as an ES module or directly in the browser as a universal module (umd). For this part I decided to use Vite and for the most part is was pretty straightforward. The configuration looks like this:

import { resolve } from 'path'
import { defineConfig } from 'vite'

export default defineConfig({
    build: {
        lib: {
            formats: ['umd', 'es'],
            entry: resolve(__dirname, 'src/index.ts'),
            name: 'SimpleContents',
            fileName: 'index',
        }
    }
})

One small problem I had with the umd output was the extension .cjs. I wanted it to be just .js. So I had to do this:

"scripts": {
    ...
    "build": "tsc && vite build && mv dist/index.umd.cjs dist/index.umd.js",
    ...
  },

I had to do this because NPM sets a mime type of application/node for the extension .cjs. That was not working well with JSDeliver out of the box. There is probably a better way to do this but I wasn't feeling like spending too much time on it.

CI

After having all the code ready it was time to make two GitHub workflows, one for all push events and one for versioning and publishing on NPM.

For the push events it is basically just doing npm install and running npm run test.

For versioning I thought, let's use semantic versioning by determining the next version based on the latest "v" tag in combination with commit message flags for bumping minor and major versions.

Result

Couple days later I had this library and nice contents sections for my posts. You can check out details on GitHub.

GitHub - kickthemooon/simple-contents: Generate a contents for your posts based on headings
Generate a contents for your posts based on headings - GitHub - kickthemooon/simple-contents: Generate a contents for your posts based on headings
Adnan Mujkanovic

Adnan Mujkanovic

Full Stack Overflow Developer / YAML Indentation Specialist / Yak Shaving Expert
Gotham City