https://github.com/webpro/markdown-rambler
Yet another opinionated & powerful static site generator.
https://github.com/webpro/markdown-rambler
markdown static-site static-site-generator
Last synced: about 2 months ago
JSON representation
Yet another opinionated & powerful static site generator.
- Host: GitHub
- URL: https://github.com/webpro/markdown-rambler
- Owner: webpro
- License: mit
- Created: 2022-04-15T20:50:37.000Z (about 4 years ago)
- Default Branch: main
- Last Pushed: 2023-03-27T13:22:47.000Z (about 3 years ago)
- Last Synced: 2026-02-19T17:56:08.581Z (2 months ago)
- Topics: markdown, static-site, static-site-generator
- Language: TypeScript
- Homepage:
- Size: 2.92 MB
- Stars: 3
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Markdown Rambler
Yet 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.
## Showcase
See [webpro.nl](https://www.webpro.nl) and [github.com/webpro/webpro.nl](https://github.com/webpro/webpro.nl) for an
example website powered by Markdown Rambler.
## Input
```
.
└── content
├── articles
│ ├── starting-a-blog.md
│ ├── writing-a-blogpost.md
│ └── yet-another-article
│ ├── index.md
│ └── image.webp
├── blog.md
└── index.md
```
## Build Script
```js
const rambler = new MarkdownRambler();
rambler.run();
```
## Output
```
.
└── 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
```
## Tests
See [the tests](./test/index.spec.ts) to get an impression of the conversion from Markdown to HTML.
## Options
### Overview
#### 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`](#feed) | `false` | Generates `feed.xml` (RSS) |
| `search` | [`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`](#format-markdown) | `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`](#type) | `page` | Add `type` to each page `meta` data (e.g. `'article'`) |
| [`defaults`](#defaults) | `Record` | `undefined` | Default meta data for each document |
#### Plugins
In order of exection:
| Option | Type | Default value | Description |
| --------------------------------------------- | --------------------- | --------------------------------- | -------------------------------------- |
| [`parsers`](#parsers) | `Pluggable[]` | [`parsers`](#parsers) | Remark parsers |
| [`directives`](#directives) | `Record` | `undefined` | Directives to extend Markdown syntax |
| [`remarkPlugins`](#remark-plugins) | `Pluggable[]` | [`remarkPlugins`](#remarkPlugins) | Additional remark plugins |
| [`remarkRehypeOptions`](#remarkrehypeoptions) | `RemarkRehypeOptions` | `{}` | Options for remark-rehype |
| [`rehypePlugins`](#rehype-plugins) | `Pluggable[]` | [`rehypePlugins`](#rehypePlugins) | Additional rehype plugins |
| [`renderers`](#renderers) | `Pluggable[]` | [`renderers`](#renderers) | Plugins to render (stringify) the hast |
1. mdast: Markdown Abstract Syntax Tree
2. hast: HyperText (HTML) AST
### Feed
```ts
type Feed = {
pathname: string;
title: string;
description?: string;
author?: string;
tags?: string[];
filter?: (type: string, vFile: VFile) => boolean;
};
```
### Search
```ts
type Search = {
outputDir?: string;
filter?: (type: string, vFile: VFile) => boolean;
};
```
Generates a [MiniSearch](https://lucaong.github.io/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.
### Format Markdown
Set `formatMarkdown: true` and the following plugins will be applied to the Markdown source files:
- [remark-prettier](https://github.com/remcohaszing/remark-prettier) to format the document
- [remark-reference-links](https://github.com/remarkjs/remark-reference-links) to turn `[text](url)` into `[text][ref]`
(and add definitions to the end)
- [order-links](./src/unist/order-links.ts) to order the definitions
### Type
```ts
type TypeFn = (filename: string, matter: FrontMatter) => PageType;
```
Example:
```ts
{
type: filename => (filename.match(/^blog\//) ? 'article' : 'page');
}
```
### Defaults
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
---
# Yet Another Article
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 ``
- The `author.name` adds ``
- The `prefetch` value will add ``
See the [`PageOptions` type](./src/types.d.ts) 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`
${node}
© 2022, Lars Kappert
`;
};
```
In this example, the `class` field of the Front Matter of each Markdown file would be added to the `` element,
while the `default.page.class` option could serve as a fallback `class` value.
### Plugins
#### Parsers
The default remark plugins:
- [remark-parse](https://github.com/remarkjs/remark/tree/main/packages/remark-parse)
- [remark-frontmatter](https://github.com/remarkjs/remark-frontmatter)
- [table](./src/mdast/table.ts)
- [remark-directive](https://github.com/remarkjs/remark-directive) (also see [Directives](#directives))
These can be entirely replaced with different `parsers`, or extended using [remarkPlugins](#remark-plugins).
#### remark Plugins
Use `remarkPlugins` to add [remark plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md) (to work with
the mdast before it is converted to hast).
#### remarkRehypeOptions
Use `remarkRehypeOptions` to pass [options](https://github.com/remarkjs/remark-rehype#options) to
[remark-rehype](https://github.com/remarkjs/remark-rehype).
#### rehype Plugins
The default rehype plugins:
- [rehype-autolink-headings](https://github.com/rehypejs/rehype-autolink-headings) (only wraps h2-h6)
- [rehype-slug](https://github.com/rehypejs/rehype-slug)
- [rehype-document](https://github.com/rehypejs/rehype-document)
- [JSON-LD](./src/util/structured-content.ts) (structured content) in a
[{}](./src/hast/transformers.ts).
Use `rehypePlugins` to add [rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md) (to work with
the hast after it is converted from mdast).
#### Renderers
- [rehype-format](https://github.com/rehypejs/rehype-format)
- [rehype-stringify](https://github.com/rehypejs/rehype/tree/main/packages/rehype-stringify)
Use the `renderers` option to replace these default render plugins.
### Directives
Directives are a powerful way to extend the Markdown syntax. The (implemented) proposal consists of inline (`:`), leaf
(`::`) and container (`:::`) block directives.
```md
::ASIDE
# Header
:::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, and
adds the `vFile` 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
news
Header
Content with HTML
```