noddity-micromark-renderer v2.0.1
Noddity Micromark Renderer
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.)
Install
The usual ways:
npm install noddity-micromark-rendererUsing
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',
})API of non-legacy noddityRenderer
Each input property is defined here:
loadFile
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.
metadataMdastMutator
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 })nonMarkdownRenderer
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 thetemplateStringwill 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.jpgand 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.
urlRenderer
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}`markdownToMdast
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.
mdastToHast
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 })hastToHtml
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 })render
An initialized render is an object containing the following properties:
fromString
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.
loadFile
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
loadMetadata
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.
loadPost
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).
License
Published and released under the Very Open License.
If you need a commercial license, contact me here.