This is a re-work of Noddity that uses Micromark and mdast to render the Markdown content, and has no opinion about non-Markdown content.
Included is a legacy renderer, if you want to use classic Noddity syntax and the legacy Ractive renderer. (See the demo for an example of it.)
The usual ways:
npm install noddity-micromark-renderer
You'll need to initialize the renderer with several options (all of these are required):
import { noddityRenderer } from 'noddity-micromark-renderer'
const render = noddityRenderer({
// Noddity-specific functions
loadFile,
metadataParser,
nonMarkdownRenderer,
urlRenderer,
// Micromark-specific functions
hastToHtml,
markdownToMdast,
mdastToHast,
})
// for later examples
const NODDITY_FOLDER = '/path/to/noddity/content'
const DOMAIN = 'my-site.com'
If you're using the legacy renderer, which uses Ractive, you don't need nearly as much:
import { noddityRenderer } from 'noddity-micromark-renderer/legacy'
const render = noddityRenderer({
directory: NODDITY_FOLDER,
domain: 'site.com',
pathPrefix: '#!/',
pagePathPrefix: 'post/',
name: 'My Cool Website',
})
Each input property is defined here:
Typed: (filename: string, options?: Object) => Promise<string>
Used by the renderer to lookup Noddity templates. If you're rendering from disk, you could do:
import { join } from 'node:path'
import { readFile } from 'node:fs/promises'
const loadFile = async filename => readFile(join(NODDITY_FOLDER, filename), 'utf8')
The options
object is passed along to your non-Markdown renderer, merged into the overall options.
Typed: async ({ filename: string, mdastTree: MdastTree }): void
Called by the renderer to mutate the mdast (Markdown Abstract State Tree) to optionally parse the frontmatter section of each file and set it.
After this is called, mdast
should have these properties set to properly parse the remaining content:
mdast.children[0].metadata
- This is the fully parsed file metadata, if present.mdast.children[0].position.end.offset
- This is the unist positional information about the character offset at the very end of the metadata, including frontmatter fence characters.
If you want to use js-yaml that would be (defining the schema is not required by this renderer):
import { load, JSON_SCHEMA } from 'js-yaml'
const metadataParser = frontmatter => load(frontmatter, { schema: JSON_SCHEMA })
Typed: ({ filename: string, template: string, string: string, metadata: Object, variables: Array<NoddityVariables> }) => Promise<string>
Where NoddityVariables: { name: string, positional: boolean, value: string }
This is where you would render non-Markdown content. In classic Noddity this means Ractive (at v0.7
) styled templates, but for this renderer there are no opinions about what you should use.
The returned value is the fully rendered HTML.
Properties passed to the function:
filename: string
- The file doing the calling of this as a template, if applicable, e.g.folder/my-file.md
.innerHtml?: string
- If loading a post, this will be the fully rendered post content, while thetemplateString
will be the surrounding post non-rendered string.metadata?: Object
- The parsed metadata from the files frontmatter section.templateName: string
- The name of the template, e.g. if the above file had::img|face.jpg::
this would beimg
.templateString: string
- The string extracted from the Noddity file, e.g. in classic Noddity this would be the Ractive component.variables?: Array<NoddityVariables>
- An optional ordered list of variables passed along when calling this template.
Properties on the NoddityVariables
objects are:
name: string
- The name of the variable. For a reference like::img|face.jpg|size=big::
the first variable's name would beface.jpg
and the second would besize
.positional: boolean
- Set to true if it is not a key=value named variable.value?: string
- The value, if it is a named variable.
Typed: ({ filename: string, link: string }) => Promise<string>
It's up to you to render the correct URL string, but it's usually something like this:
const urlRenderer = ({ link }) => `https://${DOMAIN}/#!/post/${link}`
Typed: (markdown: string) => Promise<Mdast>
Async function that resolves to an mdast
(Markdown Abstract State Tree), for example mdast-util-from-markdown, and that needs to contain the Noddity-specific nodes defined in mdast-util-noddity.
Here's how you might set this function up:
import { fromMarkdown } from 'mdast-util-from-markdown'
import { frontmatter } from 'micromark-extension-frontmatter'
import { frontmatterFromMarkdown } from 'mdast-util-frontmatter'
import { gfm } from 'micromark-extension-gfm'
import { gfmFromMarkdown } from 'mdast-util-gfm'
const markdownToMdast = string => fromMarkdown(string, {
extensions: [
// if you need more extensions, e.g. for additional Markdown functionality, you'd configure it here
frontmatter([ 'yaml' ]),
gfm(),
micromarkFromNoddity(),
],
mdastExtensions: [
// (and here)
frontmatterFromMarkdown([ 'yaml' ]),
gfmFromMarkdown(),
mdastFromNoddity(),
],
})
The function must return a promise which resolves to an mdast
with the Noddity-specific nodes.
Typed: (tree: MdastTree) => HastTree
Given an mdast
tree, return an hast
(HTML Abstract State Tree).
Note that, if you use templates of any kind, you'll probably want to allow HTML. Here's one way to do this:
import { toHast } from 'mdast-util-to-hast'
const mdastToHast = mdastTree => toHast(mdastTree, { allowDangerousHtml: true })
Typed: (tree: HastTree) => string
Given an hast
(HTML Abstract State Tree) output an HTML string.
Note that, if you use templates of any kind, the hast
will contain text nodes that are HTML (as opposed to a strict hast
) so you'll probably want to allow that. Here's one way:
import { toHtml } from 'hast-util-to-html'
const hastToHtml = hastTree => toHtml(hastTree, { allowDangerousHtml: true })
An initialized render
is an object containing the following properties:
Typed: (markdown: string, virtualFilename?: string) => Promise<string>
This function is used to render free-hand sections of Markdown as noddity, e.g. instead of rendering a file you can render a chunk of Markdown with not other context.
The virtualFilename
is used only for logging purposes, and if not provided will default to VIRTUAL_FILE.md
.
Typed: (filename: string) => Promise<string>
This is a per-file renderer function. It renders a file by loading the provided filename
using the defined loadFile
function, which can load files from anywhere, e.g. from disk, database, cloud storage, etc.
It then passes through the flow Markdown -> mdast -> Noddity (templates and links) -> hast -> html
Typed: (filename: string) => Promise<Object>
This is a convenience method, which will use the defined loadFile
to read in a file and parse out the frontmatter metadata section, using your provided metadataParser
function to turn that string into an object.
Typed: (templateFilename: string, postFilename: string) => Promise<string>
Similar to the loadFile
function, except it renders the postFilename
inside the context of the templateFilename
(inside Noddity, by default this is the content/post
file).
Published and released under the Very Open License.
If you need a commercial license, contact me here.