Yet another opinionated & powerful static site generator.
npm install markdown-ramblerYet another opinionated & powerful static site generator.
Turns directories with Markdown files into static websites.
- Based on the remark and rehype ecosystems.
- Powerful and extensible plugins.
- Zero-config with sane defaults to get started.
- Directory structure and file names determine the url's.
- Front Matter in Markdown to override defaults.
- Use a page type to enable different layouts and plugins.
- Easily build layouts around the Markdown-based content and their type.
- Optimize SEO with HTML documents including (OpenGraph) meta tags and structured content (application/ld+json).
- Optimize performance by bundling CSS and JS assets.
- Mark drafts to exclude from lists (yet available with )
- Includes SVGO to optimize SVGs assets.
- Writes sitemap.txt.
- Writes RSS feed.
- Writes search index (using MiniMatch).
- Features a --watch mode for auto re-generation.
See webpro.nl and github.com/webpro/webpro.nl for an
example website powered by Markdown Rambler.
```
.
└── content
├── articles
│ ├── starting-a-blog.md
│ ├── writing-a-blogpost.md
│ └── yet-another-article
│ ├── index.md
│ └── image.webp
├── blog.md
└── index.md
`js`
const rambler = new MarkdownRambler();
rambler.run();
``
.
└── dist
├── articles
│ ├── starting-a-blog
│ │ └── index.html
│ ├── writing-a-blogpost
│ │ └── index.html
│ └── yet-another-article
│ ├── index.html
│ └── image.webp
├── blog
│ └── index.html
├── index.html
└── sitemap.txt
And all URLs in /sitemap.txt:
``
https://example.org/
https://example.org/articles/starting-a-blog
https://example.org/articles/writing-a-blogpost
https://example.org/articles/yet-another-article
https://example.org/blog
See the tests to get an impression of the conversion from Markdown to HTML.
#### File Structure & Output
| Option | Type | Default value | Description |
| --------------- | -------------------- | ----------------------- | ---------------------------------------- |
| contentFiles | string \| string[] | '*/' | Include Markdown and assets |contentDir
| | string \| string[] | ['content'] | Directories containing Markdown |ignorePattern
| | string | /^(\.\|node_modules)/ | File pattern(s) to ignore with --watch |publicDir
| | string | 'public' | Directory containing public assets |outputDir
| | string | 'dist' | Output directory |sitemap
| | boolean | true | Generates sitemap.txt |feed
| | Feed | false | Generates feed.xml (RSS) |search
| | Search | false | Generates MiniSearch index |
#### Flags
| Option | Type | Default value | Description |
| ------------------------------------ | --------- | ------------- | ---------------------------------------------- |
| verbose | boolean | false | Logs more output about the process |watch
| | boolean | false | Add watcher to re-process modified files |formatMarkdown
| | boolean | false | Formats source Markdown files (using Prettier) |
#### Content
| Option | Type | Default value | Description |
| ----------------------- | ------------------------------- | ------------- | ------------------------------------------------------ |
| host | string | '' | Host (e.g. 'https://example.org') |name
| | string | '' | Website name |language
| | string | 'en' | Website language (e.g. 'fr-BE') |manifest
| | false \| string | false | Link to PWA manifest file |type
| | TypeFn | page | Add type to each page meta data (e.g. 'article') |defaults
| | Record | undefined | Default meta data for each document |
#### Plugins
In order of exection:
| Option | Type | Default value | Description |
| --------------------------------------------- | --------------------- | --------------------------------- | -------------------------------------- |
| parsers | Pluggable[] | parsers | Remark parsers |directives
| | Record | undefined | Directives to extend Markdown syntax |remarkPlugins
| | Pluggable[] | remarkPlugins | Additional remark plugins |remarkRehypeOptions
| | RemarkRehypeOptions | {} | Options for remark-rehype |rehypePlugins
| | Pluggable[] | rehypePlugins | Additional rehype plugins |renderers
| | Pluggable[] | renderers | Plugins to render (stringify) the hast |
1. mdast: Markdown Abstract Syntax Tree
2. hast: HyperText (HTML) AST
`ts`
type Feed = {
pathname: string;
title: string;
description?: string;
author?: string;
tags?: string[];
filter?: (type: string, vFile: VFile) => boolean;
};
`ts`
type Search = {
outputDir?: string;
filter?: (type: string, vFile: VFile) => boolean;
};
Generates a MiniSearch index file to be used in your client. Here's a minimal
example of a client script to use the search index:
`js`
(async () => {
await import('https://cdn.jsdelivr.net/npm/minisearch@4.0.3/dist/umd/index.min.js');
const searchIndex = await fetch('/_search/index.json').then(response => response.text());
const index = MiniSearch.loadJSON(searchIndex, { fields: ['title', 'content'] });
const searchBox = document.querySelector('input[type=search]');
const search = query => {
const results = index.search(query, { prefix: true, fuzzy: 0.3 });
console.log(results);
};
searchBox.addEventListener('input', event => {
search(event.target.value);
});
})();
The script(s) can be added to e.g. the public folder and its path to the defaults.page.scripts array.
Set formatMarkdown: true and the following plugins will be applied to the Markdown source files:
- remark-prettier to format the document
- remark-reference-links to turn text into [text][ref]
(and add definitions to the end)
- order-links to order the definitions
`ts`
type TypeFn = (filename: string, matter: FrontMatter) => PageType;
Example:
`ts`
{
type: filename => (filename.match(/^blog\//) ? 'article' : 'page');
}
Sets default for each type of page. By default there's only the page type. Example:
`js`
const options = {
defaults: {
page: {
layout: '[See "Layout" below]'
stylesheets: ['/css/stylesheet.css'],
author: {
name: 'Lars Kappert',
href: 'https://www.webpro.nl',
twitter: '@webprolific'
},
publisher: {
name: 'Lars Kappert',
href: 'https://www.webpro.nl',
logo: {
src: 'https://www.webpro.nl/img/logo-512x512.png'
}
},
icon: {
src: '/img/logo.svg'
},
logo: {
alt: 'Blog Logo',
src: '/img/logo.svg',
href: '/'
},
sameAs: ['https://github.com/webpro'],
layout: () => {},
prefetch: '/blog'
}
}
};
Any Front Matter in the Markdown augments or overrides these defaults.
`md
---
published: 2022-03-05
modified: 2022-04-20
image: /articles/yet-another-article/image.webp
draft: true
---
Lorem ipsum
`
The merged meta data will be used in the meta tags and structured content, and is available in layouts and directives.
- The published date adds author.name
- The adds prefetch
- The value will add
See the PageOptions type for details.
#### Layout
Each page type can have its own layout to wrap the content. Render ${node} somewhere, and use all of the page's meta
data that was provided by Markdown Rambler, merged in with the provided default configuration:
`ts
import { html } from 'markdown-rambler';
export default (node, meta) => {
const { logo } = meta;
return html
;`
};
In this example, the class field of the Front Matter of each Markdown file would be added to the element,default.page.class
while the option could serve as a fallback class value.
#### Parsers
The default remark plugins:
- remark-parse
- remark-frontmatter
- table
- remark-directive (also see Directives)
These can be entirely replaced with different parsers, or extended using remarkPlugins.
#### remark Plugins
Use remarkPlugins to add remark plugins (to work with
the mdast before it is converted to hast).
#### remarkRehypeOptions
Use remarkRehypeOptions to pass options to
remark-rehype.
#### rehype Plugins
The default rehype plugins:
- rehype-autolink-headings (only wraps h2-h6)
- rehype-slug
- rehype-document
- JSON-LD (structured content) in a
.
Use rehypePlugins to add rehype plugins (to work with
the hast after it is converted from mdast).
#### Renderers
- rehype-format
- rehype-stringify
Use the renderers option to replace these default render plugins.
Directives are a powerful way to extend the Markdown syntax. The (implemented) proposal consists of inline (:), leaf::
() and container (:::) block directives.
`md
::ASIDE
:::div{.wrapper}
Content with :abbr[HTML]{title="HyperText Markup Language"}
:::
`
The inline and container directives are readily available. To use a leaf block directive, pass an object with the
directive as a key, and a function that returns a hast node. The function is much like an AST visitor function, andvFile
adds the argument for convenience:
`ts`
type DirectiveVisitor = (node: Element, index: number, parent: Parent, vFile: VFile) => Element;
`ts
const insertAside = (node, index, parent, vFile) => {
return h('aside', { class: 'custom' }, 'news');
};
const directives = {
ASIDE: insertAside
};
`
This will result in this HTML output:
`html``Header
Content with HTML