Because of the above change, we need to make some breaking changes that Bedrock users will need to change manually.
Currently there is only one. If you use any relative paths to files in core in your content folder e.g. include ../../../../core/templates/mixins/icon-overview you need to change those to absolute paths include /core/templates/mixins/icon-overview .
We will provide a migration guide to move projects from pre-1.41 to 1.41.
Another manual upgrade is in the Bedrock config. Make sure you have an entry for styleguide.codeSamples.jsx (either true or false depending on preference). Please refer to this file for the source.
Updating Bedrock
As a reminder or a hint for new users, you can upgrade an existing Bedrock install by installing the CLI, then running
bedrock upgrade
For the dev version, use:
bedrock upgrade --dev
For the canary version, use:
bedrock upgrade --canary
Why manual file changes?
Some background: Bedrock-CLI only updates changes in the core folder (and a bit of wrangling in package.json).
Any other file changes outside have to be implemented manually.
I could theoretically update Bedrock-CLI to accord to this change, but not enough people are using Bedrock and/or upgrading Bedrock installs to justify coding this.
For the period of June 15 – July 8, we worked on several features in Bedrock 1.34 to Bedrock 1.38. Highlights include:
PurgeCSS support
PostCSS support
Different configs for development and production
Minification of CSS and JS for production builds
A way to customize the page tree so that markup is not “in the way” or even remove the page tree altogether (e.g. in production builds)
These were marked as pre-release until we were sure of moving along with all of it. We’ve let go of the pre-release label and these features are now part of the master branch.
I’ve now released Bedrock 1.38.1 which contains almost all of the recent features.
Yesterday I went on one of those crazy programmer journeys where I worked for 8-9 hours more or less non-stop. In the morning I had a discussion about all sorts of tecnical site-building-things with the crew over at Routify and this got me inspired to try some new things.
A better understanding of internals
I had been working on Bedrock for a while now, with a lof of work done in June, adding all kinds of stuff (PostCSS + PurgeCSS support for Tailwind purposes, a separate config for production etc.). This made me quite familiar with the Bedrock codebase.
(For those who don’t know what Bedrock is, it is a static site generator designed to help coding designers get up extensive HTML prototypes with hundreds of pages with surrounding styleguide and component document. We use it at Mono to prototype web apps at scale. It has been maintained as an MIT-licensed codebase for the past 6 years.)
However there were still a few parts of the code that were a bit of a mystery to me. There’s server.js which runs an Express server to get your templates on demand while working in Bedrock (avoiding static rebuilds, the way Jekyll used to work) and templates.js which builds the templates when you build a static site. Essentially they do the same, they generate the site, but one of them is at runtime based on a web request and another is when you do a static build. We will get back to this.
The site that you are reading this exact blog post on runs on WordPress (unless you are using a feed reader in which I salute you). I made this choice mostly because Bedrock never supported blogging in the first place.
The project: convert bedrockapp.org from WordPress to Bedrock itself
Now, because of the new features in Bedrock, I thought I might be able convert the Bedrock website to make it run using Bedrock. After all, since Bedrock is supposedly ready for production; why not show what we can do?
I also had reasons to dig into Express. I want to have more control for multi language setups. I want to know everything that is going on technically so I can fix problems if they arise.
So I set out in the afternoon to build new functionalities with the main focus being converting the whole website from a WordPress-driven website to one that runs on Bedrock.
For context… I am a coding designer
The initial code for Bedrock was written over 6 years ago by ex-colleague Thomas and over the years I’ve mostly maintained it, added new things but the core of it has mostly stayed the same. Thomas helped us to modernize a few things a couple of years ago too to make sure we were ready to use the next generation of JS inside of Bedrock.
I am not a programmer in my day-to-day job; but I am slowly but surely learning things. This blog post is mostly for my own reference and I doubt anyone will get through the whole thing. I like to write these kinds of things down because I know it will help me in the future.
Then I looked into the WordPress repo for this website. I copied over images and SCSS and started converting the homepage. I used html2pug directly inside of my favorite editor TextMate using Filter through command to quickly convert HTML into Pug.
I got the homepage up in seconds and everything went smooth. The SCSS pipeline was similar as the WordPress site and the homepage is more or less static.
On the homepage we have a dark hero area which doesn’t exist on the other pages. So there’s a bit of code I want to post here where we are checking if we are on the homepage or if we’re not.
if !pathname || pathname.match(/^index$/)
img(src='/images/logo-white.svg')
else
img(src='/images/logo-black.svg')
.u-sr-accessible Bedrock
Bedrock exposes the pathname on the template level which helps to make the page react to the current URL. I realize this variable is not that logically named. Above code can be handy; for selected states please refer to practical tips.
Rebuilding the documentation part
I then got to the documentation part. I figured I wanted to keep my documentation in markdown files. Currently the docs consist out of 15-ish pages. They are categorized in the sidebar but technically they are not categorized and really have the same structure:
/documentation/page-name
In Bedrock I created the markdown files in content/templates/documentation along with a documentation template, that would use marked to process the content:
Note how we are using != in Pug to render an unescaped string (see Interpolation).
Now, to make the connection between the pages I first set up that on a server level, if you would hit the /documentation/ part of the website with something behind that URL e.g. /documentation/my-page a different behavior would occur.
This line essentially uses a new file called content-docs.js (#) which exports a discover() function, which essentially looks in the right path (using this a path defined in paths.js) to find the aforementioned markdown files.
The code in content-docs.js processes the markdown files using a package called front-matter. Essentially we extract the content of the markdown file to a JS object along with anything contained in the front-matter. The resulting JS object looks a bit like this:
This can then be used to build out the docs page. I’ll repeat the final line in the template here:
.c-content.c-sheet!=marked(doc.body)
Do you see how the doc const from above circles around to the template?
In another part of already shown code above we are referencing a template documentation/_template/doc . This is in a folder with an _ and will get ignored when doing a static build.
So the documentation worked, but only when running Bedrock using npm start (where the Express server is used).
What I had to do now was also make sure that the documentation part got generated statically when running npm run build.
In general, we are using Gulp as a JS task runner. When you start up Bedrock, Gulp is used to compile Javascript, preprocess your CSS and more. When you compile the static site, Gulp is also used to compile the templates. That’s exactly the functionality that I needed to modify.
I also made sure this task ran as part of the regular build workflow.
Now in templates.js I used the same strategy I used before – carefully looked at the code that already exists, and customize the parts I needed. In this code we are using a function called getDefaultLocals to get in the content to render it. It takes the aforementione discover functions to return JS objects that we can use to render out the content.
I made a copy of the existing docs functionality (which is for rendering out docs in the styleguide, supporting Pug and Markdown templates) and made a new version called contentDocs:
There is a lot going on here, but essentially we are doing the same as on the server: taking a template and applying it to content. The stream part of the code is to make it work with gulp.
Here, we are not doing it based on a single user request but we are processing a series of files. paths.content.contentDocs.renderTemplate refers to the aforementioned doc.pug ; using gulp-data we make sure the markdown content is available for the template to render.
So I got this part working:
you could see a documentation page while developing using the server
You could render out all documentation pages to static HTML
Rebuilding the blog part
I then tried to tackle the blog functionality. I got that working on the server first.
I used the WordPress Export functionality to grab an XML file with all my content in there. I then used a script to convert everything to files:
2015-12-22-welcome-to-bedrock.md
2015-12-23-a-quick-homepage-for-bedrock.md
2015-12-29-jade.md
2015-12-30-a-static-site-generator-history.md
2016-01-25-color-guide.md
2016-07-07-adding-selected-state-to-a-navigation-in-bedrock.md
...+53 more posts (59 blog posts in total)
I then set up things in similar ways as the documentation, thinking about the URLs:
app.get('/blog/:year/:month/:day/:title', function (req, res) {
const blogFilename = req.params.year + '-' + req.params.month + '-' + req.params.day + '-' + req.params.title
let post = _.find(blogPosts.discover().allBlogPosts, post => post.attributes.filename === blogFilename);
renderView(req, res, 'blog/_template/post', {
pathname: path.join('blog/', blogFilename),
post
});
});
This code so that people can visit /blog/:year/:month/:day/:title e.g. /blog/2021/07/04/my-post .
I would take the filename of the markdown file and process it from something like 2015-12-22-welcome-to-bedrock.md to 2015/12/22/welcome-to-bedrock using some simple replace logic:
I got the blog working quite quickly and I was pleased with myself.
For the overview I applied the .sort().reverse() methods so that the data would be from new to old.
For the index page I wrote some basic pagination logic.
-
let perPage = 10;
let lower = 0 + parseInt(page) * perPage;
let higher = perPage + parseInt(page) * perPage;
let posts = blogOverview.slice(lower,higher)
// Pagination
let hasNewerPage = false;
let hasOlderPage = false;
let olderPage;
let newerPage;
if (parseInt(page) === 1) {
hasOlderPage = true;
olderPage = parseInt(page)+1
} else if (parseInt(page) > 1) {
hasOlderPage = true;
hasNewerPage = true;
olderPage = parseInt(page)+1
newerPage = parseInt(page)-1
}
I really need to remember what slice() does because I always have to look it up.
Then depending on the existence of a newer or older page, show the pagination button:
.c-pagination
if hasNewerPage
a.c-btn.previous(href='/blog/'+newerPage) Newer
if hasOlderPage
a.c-btn.next(href='/blog/'+olderPage) Older
For clarity, on the blog index we are looping over blogOverview as follows:
each post in posts
.c-article
header.c-article-header
h2
a(href="/blog/"+post.url) #{post.attributes.title}
p
| Posted on #{post.attributes.date}
if post.attributes.time
| at #{post.attributes.time}
if post.attributes.author
| by #{post.attributes.author}
.c-article-body.c-content!=marked(post.body)
We are using the aforementioned front matter attributes. This time with a bit more metadata:
---
title: "PurgeCSS support in Bedrock 1.34"
date: "2021-06-17"
author: Wolfr_
---
I could bother to make all the dates to be a standardized format but I didn’t. Somehow the export didn’t grab the post time but only the date. Since I sometimes write multiple posts in one day, this also creates an implementation problem. Ugh.
So all in all I was happy to have a blog working. You could go from the index page to the detail page and use the pagination to see all the posts. Awesome.
Then as with the documentation pages I realized I needed to make this work for the static build, else I could never finish the project. I spent some time trying to get this to work but I realized I needed:
A format where the folders follow the generation logic (i.e. instead of a flat folder with a bunch of markdown files, folders for every year/month
OR Generate said folders with a script on build time based on the contents of my flat folders
I don’t know enough about NodeJS and fs to comfortably do (2). I could do (1) and the aforementioned WordPress export plugin supports the generation of folders.
If I would ever make this a framework for other people to use I would force people to deal with a whole folder structure to make a new blog post, which is a pain.
Conclusions
So now I am in a bit of a pickle.
First of all, how to continue this work? It’s obviously not finished, because of the missing functionality to export the blog statically.
Should I push on? Shouldn’t I just keep this WordPress site running? I learned what I wanted to learn, does the world need another blogging engine that has less features than WordPress? I don’t like static blogging in practice anyway. I need a CMS and that is a whole problem case in itself.
Second, in Bedrock, does it really make sense to reimplement all functionality twice? First for the server use case and then for the static generation?
I guess it does for the core functionality of Bedrock.
The static site generation is awesome for CI purposes. We use this all the time at work. We will deploy prototypes and their different branches and all of it will be very easy with the setup that we have in place.
I suppose I could make a fork of Bedrock that is server-side only and that -has no build. But then I would also encounter my skill ceiling.
I have no knowledge setting up Express servers to deploy on the internet. I suppose it is a new skill I could learn. Heroku is the first thing that comes to mind. Then maybe I could also experiment with posting forms to the server and saving things to a database.
Oh, all the things I could learn!
For now I am just happy to have learned more about Bedrock’s internals. You know, for if I ever need to fix something to the actual scope of the project.
It used to be the case that we assumed that if you used Bedrock, you would make a prototype, and it would require a few changes to bring it to production.
For example, a typical process would be that we’d make an HTML/CSS prototype and a JS or PHP developer would take our templates and then extract these to the real app context.
We would write HTML and CSS, document some components, but leave the code non-minified.
If you’d look at most Bedrock installs closely you’d also see a specific div structure to make the page tree feature work.
If you’d look at the JS output you’d also see a relatively large bundle that is meant to make the styleguide work.
Lately I was thinking about why we have this distinction: why can’t Bedrock be the tool to also ship the production project?
We already had a build pipeline, we already had a config file. With some additions I thought we could make it work.
If you’ve been following along for the past few days you might’ve seen a lot of activity on the Bedrock repository.
Spurred by 2 recent projects that each came with their own requirements, the stars aligned and I got really deep into extending Bedrock. The result: it is now possible to use Bedrock to build sites for production.
The default output of the first page of Bedrock now looks like this:
<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bedrock</title>
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<p>Welcome to Bedrock. This is your homepage. Go to <code>/content/templates/index.pug</code> to find this file and start editing your project.</p>
<script src="/js/bundle-client.js"></script>
</body>
</html>
That’s it. That’s the code. Now, with this output, there is no styleguide and page tree feature. If you want to enable those, you can do so in the configuration. There are now separate config files for development and production.
To get the above output, the config flags you need (in bedrock.config.prod.js) are:
/**
* Bedrock production configuration
* ---
* This object will get merged into the bedrock config object and contains specific values for production
* Use `NODE_ENV=production npm run build` to run a production build
*/
module.exports = {
noIndex: false,
pageTree: false,
styleguide: false,
js: {
minify: true
},
css: {
minify: true,
purge: true,
compiler: 'scss'
},
};
This will give you minimal, compressed output it the build.
In this setup we never load the big JS file to make the prototype work (the one that contains jQuery (legacy code see #367), Codemirror for syntax highlighting etc.).
How did we come up with a single-command install for Bedrock and how does it work? The logic relies on two things: degit and npx.
The degit command allows you to clone a repository without the Bedrock Git repository itself (since you won’t be working on Bedrock, but on your own project). Degit was created by Rich Harris of Svelte fame.
Npx is a node feature since Node 5.2. It’s a package runner that allows you to run a allows you to run a command without having to install it.
The combination of these is what allows you to install Bedrock with a single command, provided you have a recent version of Node installed.
Bedrock provides two default variables: htmlClass and bodyClass. When you want to set an HTML class for every page, simply go to _layouts/master.pug and find these lines:
block pageVariables
//- Use this block to append page variables in templates
- var projectTitle = "Bedrock"
- var htmlClass = ""
- var bodyClass = ""
Now you can change the empty string to something that you like.
If you want to only add a certain body class in a specific template, use Pug’s template inheritance to append to the block. For example, let’s say you already have a utility class to set full height on the pageVariables block in master.pug, but you want to set a specific body class on another page, it will look like this.
In master.pug:
block append pageVariables
- var htmlClass = "u-full-height"
- var bodyClass = "u-full-height"
in your template (do repeat the original class value):
block append pageVariables
- var bodyClass = "u-full-height your-specific-class"
You can use this technique to set global variables or page-specific variables; or even page-specific variables that inherit to deeper templates. You could probably be more clever than this code and append to the original value too. Anyone knows how?