SSG is a static site generator implemented in CommandBox.
Latest Version: 0.1.2
box install commandbox-ssg
Installing commandbox-ssg will provide the ssg
namespace which allows for these commands:
_site
directoryssg build
whenever a change is detected in the current working directoryserver.json
and .htaccess
files if they do not exist.box
mkdir my-test-site --cd
echo "### Hello from commandbox-ssg" > index.md
ssg build
ssg serve
Commandbox-ssg works using ColdBox modules you already know and love. Markdown is processed by the cbMarkdown module to return the generated HTML. Every markdown template has can have its own metadata associated with it, called front matter, using YAML and that is processed using the cbYaml module.
When you run ssg build
it will scan the current working directory and all subdirectories for Markdown and CFM files. Every file it finds it will attempt to read the front matter from the template and that data will be added to the prc
scope for that page. The prc
scope is nod to its ColdBox roots, every page has its own prc
that encapsulates all the data required to render a page.
As noted, commandbox-ssg
checks every template for front matter and will add it to the pages prc
scope if it exists. If you do provide front matter, it is required to start on the first line, otherwise it will be ignored. How it is denoted is determined by the template type.
As an example, here is a front matter block for a markdown template.
---
type: project
view: page
title: commandbox-ssg
---
Front matter start and front matter end is marked by ---
at the beginning and the end of the block.
CFML templates work much the same way, the only difference being the document is expected to start with a ColdFusion comment block.
<!---
layout: main
view: page
--->
Just like you can write templates in liquid markdup on 11ty, commandbox-ssg
will allow you to generate pages using CFML. This allows you more granular control over how the page is generated. I leverage this to generate sitemaps and rss feeds.
You can structure you site any way you like, but there are some directories commandbox-ssg
will automatically use if they exist.
#renderedHTML#
variable_includes
folder contains the views for your site. Views will reference prc.content
to wrap the rendered content. (See below)collections
scope. This has not been implemented yet.Templates currently have two main scopes available to them as well as a helper function to generate slugs.
PRC Scope The prc
scope contains everything known about a template. Here is an example of how it might look.
{
"view": "",
"content": "<cfdump var=\"#serializeJSON(prc)#\" />",
"permalink": "/",
"fileExt": "html",
"excludeFromCollections": false,
"fileSlug": "index",
"layout": "main",
"published": true,
"date": "",
"type": "page",
"outFile": "/Users/rob/Development/small-blog/_site/index.html",
"inFile": "/Users/rob/Development/small-blog/index.cfm",
"title": "",
"build_start": 1703167890264,
"description": ""
}
Any data from front matter will be in this scope as well. Any variable from the prc
scope can be used in a layout, view, or template during the rendering process.
Some properties you may want to override in the front matter might include:
_includes
file is used when the template is rendered.main
type
rendering and use the specified viewfalse
it will not be processed when the static site is builtpost
will be sorted by publishDate in descending orderThe prc.content
variable will either contain the CFML markup to generate the page or the HTML generated from the markdown in the template. If there is a type
or view
associated with the content it wil be used. Here is an example of how content is displayed in the page
view.
Collections scope
While the prc
scope is specific to a page, the collections
scope keeps track of all known pages (unless the excludeFromCollections flag is set) and sorts them out in to several buckets. The collections.all
array contains all discovered documents. There is also a collections
array for each type of document, so if you had pages with types of post
and page
there would also be corresponding collections.post
and collections.page
arrays. It also builds out a list of all tags for your posts which can be found in collections.tags
. Finally, it also keeps track of posts by tag in teh collections.byTag.<tag>
arrays.
Configuration is handled using ssg-config.json
{
"outputDir": "_site",
"passthru": [
"favicon.ico",
"assets"
],
"ignore": []
}
Settings:
_site
If you do not explicitly exclude a directory it will be included by default. The _includes
directory is excluded by default.
Creating your first layout is simple. This is the primary wrapper for your content and the main layout is expected to exist in _includes\layouts\main.cfm
. Inside the layout you will need to specify where dynamic content should go by using #renderedHtml#
variable.
All layouts should be implemented in CFML.
<!---
site:
domain: https://example.com
name: My awesome site
description: My site description
author: commandbox-ssg
--->
<cfoutput>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>#prc.site.name#</title>
<meta name="description" content="#prc.site.description#">
<meta name="author" content="#prc.site.author#">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/themes/prism-okaidia.min.css" integrity="sha512-mIs9kKbaw6JZFfSuo+MovjU+Ntggfoj8RwAmJbVXQ5mkAX5LlgETQEweFPI18humSPHymTb5iikEOKWF7I8ncQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="/assets/css/site.css?v=#dateFormat(now(), "yyyy-mm-dd")#T#timeFormat(now(), "HH:mm:ss")#">
</head>
<body style="padding-top: 70px;">
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">#prc.site.name#</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="##navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/">Home</a>
</li>
</ul>
</div>
</div>
</nav>
<!---Container And Views --->
<div class="container-fluid">
#renderedHtml#
</div>
<footer class="border-top py-3 mt-5">
<div class="container">
<small>#prc.site.author# © 2020 - #dateFormat(now(), "yyyy")#</small>
</div>
</footer>
<!---js --->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://unpkg.com/[email protected]/dist/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/prism.min.js" integrity="sha512-YBk7HhgDZvBxmtOfUdvX0z8IH2d10Hp3aEygaMNhtF8fSOvBZ16D/1bXZTJV6ndk/L/DlXxYStP8jrF77v2MIg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</body>
</html>
</cfoutput>
Views work a little bit differently than layouts since the view wraps our template content. The HTML for our template will come from the prc.content
scope.
All views should be implemented in CFML.
<cfoutput>
<div class="row">
<div class="col-lg-8 col-md-12">
<div class="card rf-card-bordered text-white">
<div class="card-body">
<div class="card-text decorate-links">
#prc.content#
</div>
</div>
</div>
</div>
<div class="col-lg-4 d-none d-lg-block d-xl-block">
<cfinclude template="sidebar.cfm" />
</div>
</div>
</cfoutput>