Building websites with AkashaCMS and PDF Document Maker

PDF Document Maker is an integrated application, built from AkashaCMS components, for producing good-quality PDF documents from Markdown or AsciiDoc files.

In the PDF Document Maker Guide, we learned how to build a PDF document with this tool.

The same content can be reused between a PDF document and a website. The project website (https://akashacms.github.io/pdf-document-construction-set/index.html) is an example of an AkashaCMS website containing the PDF Document Maker Guide as both HTML and PDF. In both cases, this content is built from the same file.

This page is meant to show how to build a website documenting an open source software project (PDF Document Maker), and to reuse part of that website to build a downloadable standalone PDF documenting how to use the application.

The same pattern could be used by other open source software projects.

Quick overview of AkashaCMS website project configuration

The name AkashaCMS is an umbrella term over a group of software components. AkashaRender is the core, and is primarily tasked with rendering content into websites. Mahabhuta is the engine for DOM manipulation. The plugins extend the capabilities, and the Bootstrap theme component helps with making the website prettier.

The AkashaCMS website has lots of documentation of AkashaCMS project configuration.

Rather than replicate all that material, this document will show by example how this works.

The package.json is used for AkashaCMS or PDF Document Maker project administration

An AkashaCMS, or PDF Document Maker, project directory contains a package.json file containing the administrative information about the project, a Configuration file describing the structure of the project, and content files from which the website or document is produced.

The package.json file comes from the Node.js ecosystem. The prototypical use for this file is informing the npm package registry (npmjs.com) the characteristics of a Node.js package.

An AkashaCMS project, or a PDF Document Maker project, uses this file in a slightly different way. Neither a website project, nor a PDF document project, is suitable for distribution through the npm registry. Instead, we use this file to record the dependencies required to build the website or document, as well as the build and deployment processes.

In https://github.com/akashacms/pdf-document-construction-set/blob/main/guide/package.json we see an example.

The scripts section contains scripts for building the website, and the PDF document, as well as deploying the website to GitHub Pages.

"scripts": {
    "preview": "live-server out",
    "build:site": "npx akasharender render --copy-assets config.mjs",
    "build:guide": "npm-run-all build:sample-1 build:sample-2 build:workflow build:mahabhuta build:render pdf2out",
    "build:sample-1": "npx mmdc -i documents/guide/img/simple-sample-1.mmd -o documents/guide/img/simple-sample-1.svg --outputFormat svg",
    "build:sample-2": "npx mmdc -i documents/guide/img/simple-sample-2.mmd -o documents/guide/img/simple-sample-2.svg --outputFormat svg",
    "build:workflow": "npx mmdc -i documents/guide/img/pdf-document-workflow.mmd -o documents/guide/img/pdf-document-workflow.svg --outputFormat svg",
    "build:mahabhuta": "npx mmdc -i documents/guide/img/mahabhuta-workflow.mmd -o documents/guide/img/mahabhuta-workflow.svg --outputFormat svg",
    "build:render": "node ../pdf-document-maker.mjs --partial-dir partials --layout-dir layouts --document-dir documents --lesscss style.css.less --pdf-output PDF --html-output out --title 'PDF Document Maker Guide' --format A4 --funcs ./mahafuncs.mjs guide/guide.md",
    "pdf2out": "cp PDF/guide.pdf out/guide/guide.pdf",
    "gh-pages": "npx akasharender gh-pages-publish config.mjs"
},

The preview script lets us view the formatted content before deploying to a live server.

The build:site script renders the full website content into the HTML output directory. The --copy-assets parameter says to copy files from the assets directories. The file, config.mjs, is the configuration file, which is discussed in the next section.

The other build: scripts deal with rendering and formatting the PDF document. A side note is that the commands in this group which use mmdc (the Mermaid CLI) render Mermaid documents into their matching SVG file. The website also needs to display these SVG files, but in build:script these other files are not rendered, and therefore the PDF needs to have been rendered before running build:script.

The gh-pages script handles deploying the website to the GitHub repository using the GitHub Pages feature.

The time required to execute these on a mid-2016 x86 laptop, 16 GB of memory, on Ubuntu Linux, is:

$ time npm run build:guide
...
real	0m20.722s
user	0m14.697s
sys	0m4.321s

$ time npm run build:site
...
real	0m3.535s
user	0m3.491s
sys	0m0.497s

It is quite easy to do this on a developers laptop or in a GitHub Actions script. One consideration for the latter is that under the covers of PDF Document Maker is Puppeteer, which relies on the Chromium web browser running in headless mode. The memory requirements for that may exceed (this hasn't been tested) the capacity of GitHub Actions.

Configuration file

Many of the scripts just mentioned reference the configuration file, https://github.com/akashacms/pdf-document-construction-set/blob/main/guide/config.mjs

An AkashaCMS configuration file is a JavaScript file where the goal is to instantiate and initialize a Configuration object. This file is then used at every step of rendering content files to create the website or PDF document.

The configuration file includes:

Lists of directories for input files, as well as the HTML Rendering output directory:

config
    .addAssetsDir('assets')
    .addAssetsDir({
        src: 'node_modules/bootstrap/dist',
        dest: 'vendor/bootstrap'
    })
   .addAssetsDir({
        src: 'node_modules/jquery/dist',
        dest: 'vendor/jquery'
    })
    .addLayoutsDir('layouts')
    .addDocumentsDir('documents')
    .addPartialsDir('partials')
    .setRenderDestination('out');

All but the last of these are directories holding input files. They fall into four groups:

  • assets are files which are simply copied to the HTML output directory
  • layouts are the page layout templates
  • partials are the templates used for snippets
  • documents are the content files

The last, setRenderDestination, is the HTML output directory. All files are either copied into or rendered into this directory tree. It results in a directory tree which may be directly deployed to a normal web server.

In this case the gh-pages command uploads it to GitHub such that it is automatically published via GitHub Pages.

The plugins to be used, along with any configuration settings:

import { ThemeBootstrapPlugin } from '@akashacms/theme-bootstrap';
import { BasePlugin } from '@akashacms/plugins-base';
import { BreadcrumbsPlugin } from '@akashacms/plugins-breadcrumbs';
import { BooknavPlugin } from '@akashacms/plugins-booknav';
import { BlogPodcastPlugin } from '@akashacms/plugins-blog-podcast';

config
    .use(ThemeBootstrapPlugin)
    .use(BasePlugin, {
        generateSitemapFlag: true
    })
    .use(BreadcrumbsPlugin)
    .use(BooknavPlugin)
    .use(BlogPodcastPlugin);

AkashaCMS plugins extend the capabilities in different ways. For example the BreadcrumbsPlugin builds a navigation trail like this:

» PDF Document Maker » PDF Document Maker Guide » Building websites with AkashaCMS and PDF Document Maker

Possible configuration for the Markdown-IT engine, including adding Markdown extensions:


import { default as MarkdownItAttrs } from 'markdown-it-attrs';
import { default as MarkdownItDiv } from 'markdown-it-div';
config.findRendererName('.html.md')
    .use(MarkdownItAttrs, {
        allowedAttributes: [ 'id', 'class', 'caption', 'data' ]
    })
    .use(MarkdownItDiv);

This, findRendererName('.html.md'), accesses the component for rendering Markdown to HTML. It then adds two Markdown extensions.

Listing stylesheet and JavaScript files to be used on all webpages:

config
    .addFooterJavaScript({
        href: "/vendor/jquery/jquery.min.js"
    })
    .addFooterJavaScript({
        href: "/vendor/bootstrap/js/bootstrap.min.js"
    })
    .addStylesheet({
        href: "/vendor/bootstrap/css/bootstrap.min.css"
    })
    .addStylesheet({
        href: "/vendor/bootstrap/css/bootstrap-theme.min.css"
    })
    .addStylesheet({
        href: "/style.css"
    });

These file references are stored in a list, and a custom HTML element turns this list into a series of <link> or <script> tags.

If there is one or more blogs, configuring the characteristics of each:

config.plugin('@akashacms/plugins-blog-podcast')
    .addBlogPodcast(config, "news", {
        rss: {
            title: "PDF Document Maker News",
            description: "News announcements for the PDF Document Maker project",
            site_url: "http://akashacms.github.io/pdf-document-construction-set/blog/index.html",
            managingEditor: 'David Herron',
            webMaster: 'David Herron',
            copyright: '2025 David Herron',
            language: 'en',
            categories: [
                "Node.js", "Content Management System", "HTML5", "Static website generator"
            ]
        },
        rssurl: "/blog/rss.xml",
        matchers: {
            layouts: [ "blog.njk" ],
            rootPath: 'blog/'
        }
    });

This says the news blog is made of files within the VPath space starting with "blog/", and who are formatted using the blog.njk layout template. The blog produces an RSS feed at /blog/rss.xml, and the rest are parameters held inside the RSS feed.

Tour of important files in the PDF Document Maker website

The repository » PDF Document Maker » PDF Document Maker Guide » PDF Document Maker Guidehttps://github.com/akashacms/pdf-document-construction-set contains:

  • The source for the PDF Document Maker application which is distributed through the npm repository
  • A test suite for this package
  • An examples directory to demonstrate its usage
  • A guide directory containing the project documentation, both the website (including the page you're reading right now) and the PDF version of the users guide.

The files corresponding to the PDF Document Maker application package are:

$ tree assets/ built-in.mjs layouts/ pdf-document-maker.mjs package.json README.md 
assets/
└── vendor
    └── printcss
        └── print.css
built-in.mjs
layouts/
└── page.njk
pdf-document-maker.mjs
package.json
README.md

4 directories, 6 files

The files corresponding to the documentation website, and users guide PDF, are:

$ cd guide
$ tree assets partials layouts documents
assets
partials
├── footer.html
├── header.html
├── site-navbar.html
└── toc.html.njk
layouts
├── article.njk
├── blog.njk
├── home-page.njk
└── index-blog.njk
documents
├── blog
│   ├── 2025
│   │   ├── 01
│   │   │   ├── announce.md
│   │   │   └── index.html.md
│   │   └── index.html.md
│   └── index.html.md
├── guide
│   ├── guide.md
│   ├── guide-web.md
│   ├── img
│   │   ├── draw-io-export.png
│   │   ├── draw-io-export-png.png
│   │   ├── example-drawio-diagram.png
│   │   ├── mahabhuta-workflow.mmd
│   │   ├── mahabhuta-workflow.svg
│   │   ├── pdf-document-workflow.mmd
│   │   ├── pdf-document-workflow.svg
│   │   ├── simple-sample-1.mmd
│   │   ├── simple-sample-1.svg
│   │   ├── simple-sample-2.mmd
│   │   └── simple-sample-2.svg
│   └── index.md
├── index.md
└── style.css.less

8 directories, 28 files

Process for building the PDF Document Maker website

To build the website:

$ cd guide
$ npm run build:site
$ npm run gh-pages

The build:site script simply uses AkashaRender to render the site to the output directory:

"build:site": "npx akasharender render --copy-assets config.mjs",

It's expected you'll use npm run preview to preview the website locally, so you can edit the content, handle content layout issues, etc, before deploying it to the web server.

Adding announcements (blog posts)

It is very easy to add new content to an AkashaCMS website. You simply create a file in a documents directory. The next time you run the render command it will be automatically rendered into the output directory.

In the tree shown above, the announcements blog is stored in documents/blog. It is structured with a date-oriented directory tree so one can easily determine when a blog post was made. The directory tree in an AkashaCMS blog is entirely arbitrary, and the Blog-Podcast plugin does not force any file structure.

The index.html.md files in that directory hierarchy form an index of postings in the child directories to that directory. Hence, blog/2025/index.html will contain a different index from blog/2025/01/index.html.

Process for building PDF of PDF Document Maker Guide

To build the user guide PDF:

$ cd guide
$ npm run build:guide

This script is defined as so:

"build:guide": "npm-run-all build:sample-1 build:sample-2 build:workflow build:mahabhuta build:render pdf2out",

The npm-run-all program is a great way to simplify using package.json to define a build process. This runs the individual scripts to build Mermaid documents, then to run PDF Document Maker to build the PDF, and finally copy the PDF to the HTML output directory.

Once the PDF is built, it's up to you how this it is distributed. For this project, copying the PDF into the HTML output directory ensures we can distribute the PDF via the website.

Notice that the scripts for building the PDF refer to the same content files as are used to build the website. The documents directory contains guide/guide.md which produces guide/guide.html on the website, and which produces the guide.pdf PDF document.

It's the exact same file.

Process for auto-rebuild and live-reloading

By using Nodemon and Live Reload, we can improve the user experience of inspecting the results of our edits.

With what we've shown so far, after editing files you must go to a terminal and type npm run build:guide or npm run build:site. While that's not onerous, we can improve on this.

This will rely on two tools:

The Live Server application watches the directory containing HTML+CSS+JS files, and when there's a change it causes the browser to reload the page.

It is started with this package.json script:

"preview": "live-server out",

The Nodemon application is used in this case to watch the source files, and to run the build:site or build:guide script.

In the repository, the guide/package.json has these scripts tags:

"watch:site": "npx nodemon -e less,md,css,html,png,mmd \
      --watch assets --watch documents --watch layouts --watch partials \
      --exec npm run build:site",
"watch:guide": "npx nodemon -e less,md,css,html,png,mmd \
      --watch assets --watch documents --watch layouts-pdf --watch partials \
      --exec npm run build:guide",

This has been reformatted for readability. With these options, Nodemon watches the important directories, and if a change occurs it reruns the command named after --exec.

In one terminal window, run: npm run watch:site OR npm run watch:guide

In another terminal window, run: npm run preview

The last will automatically open a browser tab, and you can browse the website view of your content.

Go back to your editor, make a change, then go back to your browser, and the browser might have reloaded by the time you're able to switch windows.