npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

markdown-rambler

v0.0.23

Published

Yet another opinionated & powerful static site generator.

Downloads

1,415

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 <meta name="robots" content="noindex">)
  • 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 and 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

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 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 | 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<PageType, PageOptions> | undefined | Default meta data for each document |

Plugins

In order of exection:

| Option | Type | Default value | Description | | --------------------------------------------- | --------------------- | --------------------------------- | -------------------------------------- | | parsers | Pluggable[] | parsers | Remark parsers | | directives | Record<string, any> | 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

Feed

type Feed = {
  pathname: string;
  title: string;
  description?: string;
  author?: string;
  tags?: string[];
  filter?: (type: string, vFile: VFile) => boolean;
};

Search

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:

(async () => {
  await import('https://cdn.jsdelivr.net/npm/[email protected]/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:

Type

type TypeFn = (filename: string, matter: FrontMatter) => PageType;

Example:

{
  type: filename => (filename.match(/^blog\//) ? 'article' : 'page');
}

Defaults

Sets default for each type of page. By default there's only the page type. Example:

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.

---
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 <meta property="article:published_time" content="2022-03-05T00:00:00Z">
  • The author.name adds <meta name="author" content="Lars Kappert">
  • The prefetch value will add <link rel="prefetch" href="/blog">

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:

import { html } from 'markdown-rambler';

export default (node, meta) => {
  const { logo } = meta;
  return html`
    <header>
      <a href="${logo.href}">
        <img src="${logo.src}" alt="${logo.alt}" />
      </a>
    </header>
    <main class=${meta.class}>${node}</main>
    <footer>© 2022, Lars Kappert</footer>
  `;
};

In this example, the class field of the Front Matter of each Markdown file would be added to the <main> element, while the default.page.class option could serve as a fallback class value.

Plugins

Parsers

The default remark plugins:

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:

Use rehypePlugins to add rehype plugins (to work with the hast after it is converted from mdast).

Renderers

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.

::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:

type DirectiveVisitor = (node: Element, index: number, parent: Parent, vFile: VFile) => Element;
const insertAside = (node, index, parent, vFile) => {
  return h('aside', { class: 'custom' }, 'news');
};

const directives = {
  ASIDE: insertAside
};

This will result in this HTML output:

<aside class="custom">news</aside>
<h1>Header</h1>
<div class="wrapper">Content with <abbr title="HyperText Markup Language">HTML</abbr></div>