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 🙏

© 2026 – Pkg Stats / Ryan Hefner

metalsmith-doctoc

v0.2.1

Published

A Metalsmith plugin to generate table-of-contents menus.

Readme

metalsmith-doctoc

Please be aware that, as long as this plugin's version begins with '0.', I consider this to be a 'Under development' version. This means that it should basically work, but still needs some improvement/editing/review/testing.

The main purpose of this Metalsmith (npmjs, github) plugin is to provide a framework for lightweight plugins (LPs). metalsmith-doctoc (MDT) will invoke LPs for each file they are configured for, which they will analyze in order to generate a table-of-contents (TOC) menu tree. These TOC menu trees can then be used in combination with a template engine to render menus into your files.

Take a look at the API documentation if you are interested in creating your own plugin for MDT.

TODO

  • test require() with Options.resolveFunc()
  • validation of menu trees returned by plugins?

Installation

npm install metalsmith-doctoc

Overview

In general you will use frontmatter properties to mark your files as "to be processed":

---
doctoc-flag: default
---
file content

When such a file is encountered, MDT will use the property's value to determine which LP configuration to use in order to process that file. In the above case, "default" refers to a named configuration that MDT needs to look up inside it's own options object:

.use(doctoc({
  filter: "**",
  docotocFlag: "doctoc-flag",
  ignoreFlag: false,
  plugins: {
    "default": { plugin: "doctoc-default", options: "h1-6" }
  },
  default: "default",
  enableRequire: false,
  doctocTree: "doctoc-tree"
})

This essentially tells MDT to initialize the integrated plugin "doctoc-default" and make it use-able via the "default" configuration. This will also initialize the integrated plugin with default options defined by "h1-6".

When metalsmith's pipeline is run, MDT will execute the integrated LP when it encounters that file. 'doctoc-default' will then extract the file's TOC and generate a menu tree. Once MDT receives that tree, it will assign it to the file's 'doctoc-tree' property.

Options object

MDT's options object is defined as follows:

Options {
  //- a multimatch pattern to select which files to process.
  //- a string, or an array of multimatch strings.
  //- files that don't match (any pattern) will be ignored.
  filter: "**",

  //- if a file has a doctocFlag property, the file
  //  is marked as "to be processed".
  //- any file that does not define this property
  //  will be ignored.
  doctocFlag: "doctoc",

  //- assign the boolean value 'true' to ignore any doctocFlag
  //  frontmatter property and to use the default configuration
  //- this is equivalent to assigning a doctocFlag frontmatter
  //  property to all files and setting their value to true.
  ignoreFlag: false,

  //- plugins := { ($configName: $config)* }
  //  i.e. the plugins property holds named configurations.
  //- $configName := a name associated with this $config
  //- $config := ($name | $class | $definition)
  //  i.e. either a $name, a $class, or a $definition
  //- $name := the name of an integrated plugin.
  //  see section "Integrated Plugins" for a list of which
  //  names are supported. Options.resolveFunc($name) will
  //  be executed if a name is not supported.
  //- $class := a class type function, that must support
  //  '$instance = new $class()' expressions.
  //- $definition := { plugin: $plugin (, options: $options)? }
  //  i.e. there must be a plugin property,
  //  but the option property is optional.
  //  any other additional property will be ignored.
  //- $plugin := ($name | $class | $instance)
  //  i.e. either a $name, a $class function, or an $instance
  //- $instance := objects returned by 'new $class(...)'
  //- $options := anything that will be accepted by the
  //  corresponding plugin.
  plugins: {
    "default": { plugin: "doctoc-default", options: "h1-6" }
  },

  //- this marks the configuration to use by default.
  //  e.g. if (file[doctocFlag] == true)
  //- obviously, options.plugins must have an entry with
  //  ($configName == options.default),
  default: "default",

  //- when metalsmith-doctoc concludes that $name is
  //  not the name of an integrated plugin, it will
  //  try to execute options.resolveFunc($name).
  //- set this property to boolean true to allow
  //  metalsmith-doctoc to try executing require($name)
  //  before executing options.resolveFunc($name).
  enableRequire: false,

  //- ($class | $instance) function(string $name)
  //- assign a function that will resolve the given $name
  //  to a $class function, or a plugin $instance
  //- if you don't return a $class or an $instance, it
  //  will be assumed that the function failed to resolve
  //  $name as a reference to a plugin
  resolveFunc: undefined,

  //- to which file property to attach the resulting tree.
  //- this will replace the value of file[doctocFlag],
  //  if (options.doctocFlag == options.doctocTree)!
  doctocTree: "doctoc"
}

File metadata

file[options.doctocFlag]

file[options.doctocFlag] = $value
//- $value := (false | true | $configName | $config)
//- false := ignore this file
//- true := use the default options.plugins configuration
//- $configName := one of the names used in options.plugins
//- $config := { config: $configName (, options: $options )? }
//  i.e. there must be a config property,
//  but there may be an optional options property.
//  any other additional property will be ignored.
//- $options := anything that will be accepted by the
//  configured plugin.

file[options.doctocTree]

file[options.doctocTree] = $root
//- $root := the topmost node of the menu tree

This file property will hold an instance of a node object, which is defined as follows:

Node {
  //- a Heading object
  //- (root.heading == undefined)
  heading: $heading

  //- a number from [+0,+Infinity)
  //- (node.level == node.parent.level+X)
  //  must be true for some X in [+1,+Infinity)
  //  i.e. X does not have to be +1!
  //- (root.level == 0) and (node.level >= 1)
  //  for any other node object
  level: $level,

  //- the topmost node of the current node tree.
  //- (root.root == root); i.e. circular!
  root: Node?

  //- set to point to the node's parent node
  //- (node.parent.children[i] == node)
  //- (root.parent == undefined)
  parent: Node?,

  //- the next sibling such that
  //- if (node == node.parent.children[i]), then
  //  (node.next == node.parent.children[i+1]),
  //- this property will be undefined
  //  if there is no such node
  //- (root.next == undefined)
  next: Node?,

  //- the previous sibling such that
  //- if (node == node.parent.children[i]), then
  //  (node.previous == node.parent.children[i-1])
  //- this property will be undefined
  //  if there is no such node
  //- (root.previous == undefined)
  previous: Node?,

  //- all direct child nodes of this node
  //  i.e. (node.children[i].parent == node)
  children: [ Node* ],

  //- all direct and indirect child nodes of the
  //  sub-tree defined by the current node
  childrenAll: [ Node* ]
}

Note that the exact definition of the Node.heading property is not important for MDT. These objects must be defined by the LP that created them.

A heading object might have the following properties:

Heading {
  //- e.g. 'h1' in case of '<h1>'
  tag: $tag,

  //- e.g. '$id' in case of '<h1 id="$id">'
  id: $id,

  //- e.g. '$title' in case of '<h1>$title</h1>'
  title: $title,

  //- e.g. 2 in case of '<h2>'
  level: $level
}

List of plugins

MDT comes with the following LP:

  • doctoc-default is intended to be run after Markdown files have been converted into HTML files. It will use regular expressions to analyze these files and add id attributes to heading tags if needed. It's main purpose is to showcase how to implement a pluign for MDT.

The following plugins must be installed separately:

  • metalsmith-doctoc-jsdom (npmjs, github) will use jsdom (npmjs, github) to search for heading tags inside HTML content.
  • metalsmith-doctoc-cheerio (npmjs, github) will use cheerio (npmjs, github) to search for heading tags inside HTML content.

If you feel lucky, try searching on npmjs for more plugins.

Error handling

try {
  //- try something that might fail
} catch(error) {
  let newError = new Error("some message");
  newError.innerError = error;
  throw newError;
}

In some cases, MDT needs to create it's own error in order to point out, with which plugins configuration or file it had a problem. As this will hide the error that triggered a problem, the initial error object will be attached to the new error's object via a 'innerError' property. Check these if MDT's error messages lack the information you need to solve an issue.

License

MIT