Cuttlebelle
The react static site generator with editing in mind
💡 Why yet another static site generator?
All static site generators I have used restrict you to use one layout per page. Todays webdesign needs have outgrown this and we often find ourself either
adding code into our content pages (markdown files, liquid templates) or content into our layout pages.
That makes updating and maintaining a page hard, especially for a non-technical content author.
I needed a generator that can separate content from code as cleanly as possible while still staying a static site generator.
React comes with the component paradigm and was exactly what I’m looking for.
JSX enables a very easy templating like way to write components while still keeping the power of
javascript. No more templating languages that only do half of what you need. Use javascript to write your layouts.
Contents
Install
yarn global add cuttlebelle
npm install cuttlebelle -g
⬆ back to top
Usage
CLI
I recommend installing Cuttlebelle globally as it exposes the cuttlebelle
command to your system.
Once installed you can run it inside the rot of your project:
cd /path/to/my/project
cuttlebelle
This will generate all pages into the site
folder (unless specified otherwise).
Watch
You can also run our highly optimized watch while adding content or developing your layouts.
cuttlebelle --watch
This command will first build your pages and then watch for changes in any of them. It will dutifully only build the absolute minimum of pages once it detects
a change somewhere. It is so eager to only build those pages that it thinks are relevant that it misses sometimes. In cases where you add content from the
_sites
prop in one of your layouts for instance. I have added a new and somewhat genius trick to catch cases like that.
Introducing the double save TM
If you feel like the watch may have missed a page and you don’t want to leave your editor to complain about it to the watch, just save your file twice quickly
like a double click. The watch will detect the double saveTM and generate all pages for you again.
No generator
Sometimes you may only want to start a watch and not rebuild all pages. For that use the no-generate
option:
cuttlebelle --watch --no-generate
Help
Of course there is also a help option. Just run it with the help flag:
cuttlebelle --help
↑ back to Usage
Your content
The default folder structure divides content into the content/
folder and the layout and component react files into the src/
folder.
.
├── content/ # The content folder
│ ├── page1/ # Each folder represents a page and will be converted to `page1/index.html`
│ │ # 💡 As long as contains an `index.yml` file.
│ │
│ ├── index/ # The index folder is treated as the homepage and converted to `index.html`
│ │
│ └── page2/ # You can nest pages by nesting them in the folder structure
│ │
│ └── subpage1/ # As long as this folder has an `index.yml` file
│ # it will be converted to `page2/subpage1/index.html`
│
└── src/ # The `src` folder is where your layout lives
( 💡 All folders can be configured in your package.json
file via the cuttlebelle customizations.)
Now let’s look into one folder:
.
└── content
├── page1
│ ├── index.yml # This folder includes an `index.yml` file so it will be converted into a page
│ ├── partial1.md # The partials are all in markdown format and can have any name.
│ └── partial2.md # They are only converted if they are referenced inside your `index.yml` file
│
└── shared # A folder won’t be generated if it doesn’t have an `index.yml` file
├── component1.md # You can use such folders to share partials between pages
└── component2.md # This is just a suggestion. Partials can live anywhere.
Your index.yml
A typical index.yml
file could look like this:
layout: layout/page
title: Homepage
partials:
- /shared/header
- feature-image
- cta
- contact-cards
- /shared/footer
( 💡 All variables that are defined inside a page are available as props under { _sites }
.)
Your partials
And a typical partial.md
file could look like this:
--- # Each markdown file can hav frontmatter
layout: layout/cards # The power of cuttlebelle is each partial has it’s own layout
# The layout defaults to layout/partial if it’s undefined
title: partial4 # You can add any number of variables
cards: # Even arrays
- id: ID1 # Or objects
title: Card1
content: content for first card
- id: ID2
title: Card2
content: content for second card
---
Content
<!-- The content of the markdown file is exposed as { _body } to the props -->
( 💡 Of course all variables are again available as props to the layout by their own name.)
↑ back to Usage
The layout
The layout are all react components. You have to assign a layout to each page and partial. Each component will have a
bunch of props exposed to it.
A page layout
A typical component for a page might look like this:
import React from "react";
export default ( page ) => (
<html>
<head>
<title>{ page.title }</title>
</head>
<body>
<main>
<h1>{ page.title }</h1>
<div>{ page._partials }</div>
</main>
</body>
</html>
);
A page will receive the following props:
prop name | description |
---|
_myself | The ID of the current page |
_parents | An array of all parent pages IDs |
_sites | An object of all pages; with ID as key |
_partials | The generated HTML of all declared partials |
Plus all other variables declared inside your index.yml
.
A partial layout
A typical component for a partial might look like this:
import React from "react";
export default ( page ) => (
<article>
<h2>{ page.title }</h2>
<div>{ page._body }</div>
</article>
);
A page will receive the following props:
prop name | description |
---|
_myself | The ID of the current page |
_parents | An array of all parent pages IDs |
_sites | An object of all pages; with ID as key |
_body | The body of your markdown file |
Plus all other variables declared inside your partial.
( 💡 You can access the page your partial was called in via: page._sites[ page._myself ]
.)
↑ back to Usage
Customizations
Cuttlebelle can be customized via your own package.json
file.
( 💡 You can generate it via npm init
if you don’t have package.json
.)
See below all configuration with default values:
{
"name": "your name",
"version": "1.0.0",
"description": "Your description",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
+ "cuttlebelle": {
+ "folder": {
+ "content": "/content/",
+ "src": "/src/",
+ "site": "/site/",
+ "index": "index.yml",
+ "homepage": "index"
+ },
+ "layouts": {
+ "page": "layout/page",
+ "partial": "layout/partial"
+ },
+ "site": {
+ "root": "/",
+ "doctype": "<!DOCTYPE html>",
+ "redirectReact": true
+ }
+ },
"keywords": [],
"author": "",
"license": "ISC"
}
A breakdown:
"cuttlebelle": { # The cuttlebelle object
"folder": { # The is where we can adjust folder/file names
"content": "content/", # Where does your content live?
"src": "src/", # Where do your react layouts live?
"site": "site/", # Where do you want to generate your static site to?
"index": "index.yml", # What is the name of the file we look for to generate pages?
"homepage": "index" # What should the index folder be named?
},
"layouts": { # Your layout settings
"page": "layout/page", # What is the default layout for pages?
"partial": "layout/partial" # What is the default layout for partials?
},
"site": { # General settings
"root": "/", # What should cuttlebelle append to links?
"doctype": "<!DOCTYPE html>", # What doctype string do you want to add?
"redirectReact": true # You can disable redirecting `import` calls to the locally installed
# react instance of cuttlebelle rather than your local folder.
}
},
⬆ back to top
Build
To contribute to this still young project you need to install it’s dependencies and run a watch to transpile th files.
yarn
yarn watch
( 💡 Please look at the coding style and work with it, not against it :smile:.)
⬆ back to top
Tests
TODO
⬆ back to top
Release History
- v0.1.0 - 💥 Initial version
⬆ back to top
License
Copyright (c) Dominik Wilkowski. Licensed under GNU-GPLv3.
⬆ back to top
};