{"id":16860472,"url":"https://github.com/webpro/markdown-rambler","last_synced_at":"2026-03-04T04:31:34.081Z","repository":{"id":57673635,"uuid":"482081410","full_name":"webpro/markdown-rambler","owner":"webpro","description":"Yet another opinionated \u0026 powerful static site generator.","archived":false,"fork":false,"pushed_at":"2023-03-27T13:22:47.000Z","size":3064,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-19T17:56:08.581Z","etag":null,"topics":["markdown","static-site","static-site-generator"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/webpro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-04-15T20:50:37.000Z","updated_at":"2024-04-17T04:23:40.000Z","dependencies_parsed_at":"2025-04-11T08:56:08.674Z","dependency_job_id":"30fe2ca0-8c88-4032-8b1d-7895e7c106ec","html_url":"https://github.com/webpro/markdown-rambler","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/webpro/markdown-rambler","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webpro%2Fmarkdown-rambler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webpro%2Fmarkdown-rambler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webpro%2Fmarkdown-rambler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webpro%2Fmarkdown-rambler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/webpro","download_url":"https://codeload.github.com/webpro/markdown-rambler/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webpro%2Fmarkdown-rambler/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30071687,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-04T03:25:38.285Z","status":"ssl_error","status_checked_at":"2026-03-04T03:25:05.086Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["markdown","static-site","static-site-generator"],"created_at":"2024-10-13T14:24:26.148Z","updated_at":"2026-03-04T04:31:34.062Z","avatar_url":"https://github.com/webpro.png","language":"TypeScript","readme":"# Markdown Rambler\n\nYet another opinionated \u0026 powerful static site generator.\n\nTurns directories with Markdown files into static websites.\n\n- Based on the remark and rehype ecosystems.\n- Powerful and extensible plugins.\n- Zero-config with sane defaults to get started.\n- Directory structure and file names determine the url's.\n- **Front Matter** in Markdown to override defaults.\n- Use a page `type` to enable different layouts and plugins.\n- Easily build layouts around the Markdown-based content and their `type`.\n- **Optimize SEO** with HTML documents including (OpenGraph) meta tags and structured content (`application/ld+json`).\n- **Optimize performance** by bundling CSS and JS assets.\n- Mark **drafts** to exclude from lists (yet available with `\u003cmeta name=\"robots\" content=\"noindex\"\u003e`)\n- Includes **SVGO** to optimize SVGs assets.\n- Writes **sitemap.txt**.\n- Writes **RSS feed**.\n- Writes **search index** (using MiniMatch).\n- Features a `--watch` mode for auto re-generation.\n\n## Showcase\n\nSee [webpro.nl](https://www.webpro.nl) and [github.com/webpro/webpro.nl](https://github.com/webpro/webpro.nl) for an\nexample website powered by Markdown Rambler.\n\n## Input\n\n```\n.\n└── content\n    ├── articles\n    │   ├── starting-a-blog.md\n    │   ├── writing-a-blogpost.md\n    │   └── yet-another-article\n    │       ├── index.md\n    │       └── image.webp\n    ├── blog.md\n    └── index.md\n```\n\n## Build Script\n\n```js\nconst rambler = new MarkdownRambler();\nrambler.run();\n```\n\n## Output\n\n```\n.\n└── dist\n    ├── articles\n    │   ├── starting-a-blog\n    │   │   └── index.html\n    │   ├── writing-a-blogpost\n    │   │   └── index.html\n    │   └── yet-another-article\n    │       ├── index.html\n    │       └── image.webp\n    ├── blog\n    │   └── index.html\n    ├── index.html\n    └── sitemap.txt\n```\n\nAnd all URLs in `/sitemap.txt`:\n\n```\nhttps://example.org/\nhttps://example.org/articles/starting-a-blog\nhttps://example.org/articles/writing-a-blogpost\nhttps://example.org/articles/yet-another-article\nhttps://example.org/blog\n```\n\n## Tests\n\nSee [the tests](./test/index.spec.ts) to get an impression of the conversion from Markdown to HTML.\n\n## Options\n\n### Overview\n\n#### File Structure \u0026 Output\n\n| Option          | Type                 | Default value           | Description                              |\n| --------------- | -------------------- | ----------------------- | ---------------------------------------- |\n| `contentFiles`  | `string \\| string[]` | `'**/*'`                | Include Markdown and assets              |\n| `contentDir`    | `string \\| string[]` | `['content']`           | Directories containing Markdown          |\n| `ignorePattern` | `string`             | `/^(\\.\\|node_modules)/` | File pattern(s) to ignore with `--watch` |\n| `publicDir`     | `string`             | `'public'`              | Directory containing public assets       |\n| `outputDir`     | `string`             | `'dist'`                | Output directory                         |\n| `sitemap`       | `boolean`            | `true`                  | Generates `sitemap.txt`                  |\n| `feed`          | [`Feed`](#feed)      | `false`                 | Generates `feed.xml` (RSS)               |\n| `search`        | [`Search`](#search)  | `false`                 | Generates MiniSearch index               |\n\n#### Flags\n\n| Option                               | Type      | Default value | Description                                    |\n| ------------------------------------ | --------- | ------------- | ---------------------------------------------- |\n| `verbose`                            | `boolean` | `false`       | Logs more output about the process             |\n| `watch`                              | `boolean` | `false`       | Add watcher to re-process modified files       |\n| [`formatMarkdown`](#format-markdown) | `boolean` | `false`       | Formats source Markdown files (using Prettier) |\n\n#### Content\n\n| Option                  | Type                            | Default value | Description                                            |\n| ----------------------- | ------------------------------- | ------------- | ------------------------------------------------------ |\n| `host`                  | `string`                        | `''`          | Host (e.g. `'https://example.org'`)                    |\n| `name`                  | `string`                        | `''`          | Website name                                           |\n| `language`              | `string`                        | `'en'`        | Website language (e.g. `'fr-BE'`)                      |\n| `manifest`              | `false \\| string`               | `false`       | Link to PWA manifest file                              |\n| `type`                  | [`TypeFn`](#type)               | `page`        | Add `type` to each page `meta` data (e.g. `'article'`) |\n| [`defaults`](#defaults) | `Record\u003cPageType, PageOptions\u003e` | `undefined`   | Default meta data for each document                    |\n\n#### Plugins\n\nIn order of exection:\n\n| Option                                        | Type                  | Default value                     | Description                            |\n| --------------------------------------------- | --------------------- | --------------------------------- | -------------------------------------- |\n| [`parsers`](#parsers)                         | `Pluggable[]`         | [`parsers`](#parsers)             | Remark parsers                         |\n| [`directives`](#directives)                   | `Record\u003cstring, any\u003e` | `undefined`                       | Directives to extend Markdown syntax   |\n| [`remarkPlugins`](#remark-plugins)            | `Pluggable[]`         | [`remarkPlugins`](#remarkPlugins) | Additional remark plugins              |\n| [`remarkRehypeOptions`](#remarkrehypeoptions) | `RemarkRehypeOptions` | `{}`                              | Options for remark-rehype              |\n| [`rehypePlugins`](#rehype-plugins)            | `Pluggable[]`         | [`rehypePlugins`](#rehypePlugins) | Additional rehype plugins              |\n| [`renderers`](#renderers)                     | `Pluggable[]`         | [`renderers`](#renderers)         | Plugins to render (stringify) the hast |\n\n1. mdast: Markdown Abstract Syntax Tree\n2. hast: HyperText (HTML) AST\n\n### Feed\n\n```ts\ntype Feed = {\n  pathname: string;\n  title: string;\n  description?: string;\n  author?: string;\n  tags?: string[];\n  filter?: (type: string, vFile: VFile) =\u003e boolean;\n};\n```\n\n### Search\n\n```ts\ntype Search = {\n  outputDir?: string;\n  filter?: (type: string, vFile: VFile) =\u003e boolean;\n};\n```\n\nGenerates a [MiniSearch](https://lucaong.github.io/minisearch/) index file to be used in your client. Here's a minimal\nexample of a client script to use the search index:\n\n```js\n(async () =\u003e {\n  await import('https://cdn.jsdelivr.net/npm/minisearch@4.0.3/dist/umd/index.min.js');\n  const searchIndex = await fetch('/_search/index.json').then(response =\u003e response.text());\n  const index = MiniSearch.loadJSON(searchIndex, { fields: ['title', 'content'] });\n  const searchBox = document.querySelector('input[type=search]');\n  const search = query =\u003e {\n    const results = index.search(query, { prefix: true, fuzzy: 0.3 });\n    console.log(results);\n  };\n  searchBox.addEventListener('input', event =\u003e {\n    search(event.target.value);\n  });\n})();\n```\n\nThe script(s) can be added to e.g. the `public` folder and its path to the `defaults.page.scripts` array.\n\n### Format Markdown\n\nSet `formatMarkdown: true` and the following plugins will be applied to the Markdown source files:\n\n- [remark-prettier](https://github.com/remcohaszing/remark-prettier) to format the document\n- [remark-reference-links](https://github.com/remarkjs/remark-reference-links) to turn `[text](url)` into `[text][ref]`\n  (and add definitions to the end)\n- [order-links](./src/unist/order-links.ts) to order the definitions\n\n### Type\n\n```ts\ntype TypeFn = (filename: string, matter: FrontMatter) =\u003e PageType;\n```\n\nExample:\n\n```ts\n{\n  type: filename =\u003e (filename.match(/^blog\\//) ? 'article' : 'page');\n}\n```\n\n### Defaults\n\nSets default for each type of page. By default there's only the `page` type. Example:\n\n```js\nconst options = {\n  defaults: {\n    page: {\n      layout: '[See \"Layout\" below]'\n      stylesheets: ['/css/stylesheet.css'],\n      author: {\n        name: 'Lars Kappert',\n        href: 'https://www.webpro.nl',\n        twitter: '@webprolific'\n      },\n      publisher: {\n        name: 'Lars Kappert',\n        href: 'https://www.webpro.nl',\n        logo: {\n          src: 'https://www.webpro.nl/img/logo-512x512.png'\n        }\n      },\n      icon: {\n        src: '/img/logo.svg'\n      },\n      logo: {\n        alt: 'Blog Logo',\n        src: '/img/logo.svg',\n        href: '/'\n      },\n      sameAs: ['https://github.com/webpro'],\n      layout: () =\u003e {},\n      prefetch: '/blog'\n    }\n  }\n};\n```\n\nAny Front Matter in the Markdown augments or overrides these defaults.\n\n```md\n---\npublished: 2022-03-05\nmodified: 2022-04-20\nimage: /articles/yet-another-article/image.webp\ndraft: true\n---\n\n# Yet Another Article\n\nLorem ipsum\n```\n\nThe merged meta data will be used in the meta tags and structured content, and is available in layouts and directives.\n\n- The `published` date adds `\u003cmeta property=\"article:published_time\" content=\"2022-03-05T00:00:00Z\"\u003e`\n- The `author.name` adds `\u003cmeta name=\"author\" content=\"Lars Kappert\"\u003e`\n- The `prefetch` value will add `\u003clink rel=\"prefetch\" href=\"/blog\"\u003e`\n\nSee the [`PageOptions` type](./src/types.d.ts) for details.\n\n#### Layout\n\nEach page type can have its own layout to wrap the content. Render `${node}` somewhere, and use all of the page's meta\ndata that was provided by Markdown Rambler, merged in with the provided default configuration:\n\n```ts\nimport { html } from 'markdown-rambler';\n\nexport default (node, meta) =\u003e {\n  const { logo } = meta;\n  return html`\n    \u003cheader\u003e\n      \u003ca href=\"${logo.href}\"\u003e\n        \u003cimg src=\"${logo.src}\" alt=\"${logo.alt}\" /\u003e\n      \u003c/a\u003e\n    \u003c/header\u003e\n    \u003cmain class=${meta.class}\u003e${node}\u003c/main\u003e\n    \u003cfooter\u003e© 2022, Lars Kappert\u003c/footer\u003e\n  `;\n};\n```\n\nIn this example, the `class` field of the Front Matter of each Markdown file would be added to the `\u003cmain\u003e` element,\nwhile the `default.page.class` option could serve as a fallback `class` value.\n\n### Plugins\n\n#### Parsers\n\nThe default remark plugins:\n\n- [remark-parse](https://github.com/remarkjs/remark/tree/main/packages/remark-parse)\n- [remark-frontmatter](https://github.com/remarkjs/remark-frontmatter)\n- [table](./src/mdast/table.ts)\n- [remark-directive](https://github.com/remarkjs/remark-directive) (also see [Directives](#directives))\n\nThese can be entirely replaced with different `parsers`, or extended using [remarkPlugins](#remark-plugins).\n\n#### remark Plugins\n\nUse `remarkPlugins` to add [remark plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md) (to work with\nthe mdast before it is converted to hast).\n\n#### remarkRehypeOptions\n\nUse `remarkRehypeOptions` to pass [options](https://github.com/remarkjs/remark-rehype#options) to\n[remark-rehype](https://github.com/remarkjs/remark-rehype).\n\n#### rehype Plugins\n\nThe default rehype plugins:\n\n- [rehype-autolink-headings](https://github.com/rehypejs/rehype-autolink-headings) (only wraps h2-h6)\n- [rehype-slug](https://github.com/rehypejs/rehype-slug)\n- [rehype-document](https://github.com/rehypejs/rehype-document)\n- [JSON-LD](./src/util/structured-content.ts) (structured content) in a\n  [\u003cscript type=\"application/ld+json\"\u003e{}\u003c/script\u003e](./src/hast/transformers.ts).\n\nUse `rehypePlugins` to add [rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md) (to work with\nthe hast after it is converted from mdast).\n\n#### Renderers\n\n- [rehype-format](https://github.com/rehypejs/rehype-format)\n- [rehype-stringify](https://github.com/rehypejs/rehype/tree/main/packages/rehype-stringify)\n\nUse the `renderers` option to replace these default render plugins.\n\n### Directives\n\nDirectives are a powerful way to extend the Markdown syntax. The (implemented) proposal consists of inline (`:`), leaf\n(`::`) and container (`:::`) block directives.\n\n```md\n::ASIDE\n\n# Header\n\n:::div{.wrapper}\n\nContent with :abbr[HTML]{title=\"HyperText Markup Language\"}\n\n:::\n```\n\nThe inline and container directives are readily available. To use a leaf block directive, pass an object with the\ndirective as a key, and a function that returns a `hast` node. The function is much like an AST visitor function, and\nadds the `vFile` argument for convenience:\n\n```ts\ntype DirectiveVisitor = (node: Element, index: number, parent: Parent, vFile: VFile) =\u003e Element;\n```\n\n```ts\nconst insertAside = (node, index, parent, vFile) =\u003e {\n  return h('aside', { class: 'custom' }, 'news');\n};\n\nconst directives = {\n  ASIDE: insertAside\n};\n```\n\nThis will result in this HTML output:\n\n```html\n\u003caside class=\"custom\"\u003enews\u003c/aside\u003e\n\u003ch1\u003eHeader\u003c/h1\u003e\n\u003cdiv class=\"wrapper\"\u003eContent with \u003cabbr title=\"HyperText Markup Language\"\u003eHTML\u003c/abbr\u003e\u003c/div\u003e\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebpro%2Fmarkdown-rambler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebpro%2Fmarkdown-rambler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebpro%2Fmarkdown-rambler/lists"}