@push.rocks/smartmarkdown

Markdown utilities for modern TypeScript projects: parse Markdown into typed syntax trees or HTML, read YAML frontmatter, and convert HTML back into clean GitHub-flavored Markdown.

@push.rocks/smartmarkdown wraps the Unified/Remark ecosystem for Markdown parsing and the Turndown ecosystem for HTML-to-Markdown conversion behind a small, typed API that works in Node.js and browser-oriented ESM builds.

Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.

Install

pnpm add @push.rocks/smartmarkdown

What It Does

@push.rocks/smartmarkdown is intentionally focused:

  • Convert Markdown strings to HTML.
  • Parse CommonMark, GFM, and frontmatter into a typed syntax tree through /iso.
  • Parse YAML frontmatter into a JavaScript object, from Markdown or any other text, including frontmatter written as line comments.
  • Write YAML frontmatter in front of a text.
  • Preserve access to the original Markdown source.
  • Convert HTML strings back to Markdown using ATX headings and fenced code blocks.
  • Support GitHub-flavored Markdown through remark-gfm and turndown-plugin-gfm.

Quick Start

import { SmartMarkdown } from '@push.rocks/smartmarkdown';

const html = await SmartMarkdown.easyMarkdownToHtml(`# Hello Markdown

- fast
- typed
- practical
`);

console.log(html);
// <h1>Hello Markdown</h1>
// <ul>
// <li>fast</li>
// <li>typed</li>
// <li>practical</li>
// </ul>

API

parseMarkdown(source) — portable syntax tree

import { parseMarkdown, type TMarkdownRoot } from '@push.rocks/smartmarkdown/iso';

const tree: TMarkdownRoot = parseMarkdown('# Hello **world**');
console.log(tree.children[0].type); // 'heading'

The synchronous parser returns a fresh mdast tree with source positions. It supports CommonMark, GitHub-flavored tables, task lists, strikethrough, footnotes, and YAML/TOML frontmatter nodes. Frontmatter remains raw syntax; use the HTML result API below when parsed YAML values are needed.

The /iso entrypoint loads only the parser. It does not load HTML renderers, Turndown, or YAML conversion, perform I/O, execute code/HTML, or fetch image/link destinations. It works in Node.js and browser ESM bundles. The root entrypoint also exports parseMarkdown and the TMarkdownRoot, TMarkdownNode, TMarkdownRootContent, and TMarkdownPhrasingContent types.

SmartMarkdown.easyMarkdownToHtml(markdown)

Static convenience method for the most common path: Markdown in, HTML out.

import { SmartMarkdown } from '@push.rocks/smartmarkdown';

const html = await SmartMarkdown.easyMarkdownToHtml('# Hi!');

console.log(html);
// <h1>Hi!</h1>

new SmartMarkdown().getMdParsedResultFromMarkdown(markdown)

Parses a Markdown string into an MdParsedResult instance.

import { SmartMarkdown } from '@push.rocks/smartmarkdown';

const smartMarkdown = new SmartMarkdown();

const result = await smartMarkdown.getMdParsedResultFromMarkdown(`---
title: Smart Docs
published: true
tags:
  - markdown
  - docs
---

# Smart Docs

Markdown with metadata.
`);

console.log(result.originalString); // the original Markdown input
console.log(result.html); // rendered HTML
console.log(result.frontmatterData); // { title: 'Smart Docs', published: true, tags: ['markdown', 'docs'] }

The returned object exposes:

  • originalString: the Markdown input string.
  • html: the rendered HTML output.
  • frontmatterData: parsed YAML frontmatter as Record<string, unknown>.
  • title: currently initialized as an empty string for consumers that want to attach title metadata.

new SmartMarkdown().htmlToMarkdown(html)

Converts HTML back to Markdown using Turndown with GitHub-flavored Markdown support.

import { SmartMarkdown } from '@push.rocks/smartmarkdown';

const smartMarkdown = new SmartMarkdown();

const markdown = smartMarkdown.htmlToMarkdown(`
<h1 id="hello">Hello</h1>
<p>This came from HTML.</p>
<ul>
  <li>tables, strikethrough, and task lists are handled through GFM support</li>
</ul>
`);

console.log(markdown);
// # Hello
//
// This came from HTML.

Frontmatter

YAML frontmatter is read with parseFrontmatter (below) and parsed through @push.rocks/smartyaml; leading TOML frontmatter is detected with remark-frontmatter and kept out of the HTML.

import { SmartMarkdown } from '@push.rocks/smartmarkdown';

const smartMarkdown = new SmartMarkdown();

const result = await smartMarkdown.getMdParsedResultFromMarkdown(`---
layout: guide
draft: false
order: 10
---

# Guide
`);

console.log(result.frontmatterData.layout); // 'guide'
console.log(result.frontmatterData.draft); // false
console.log(result.frontmatterData.order); // 10

If no frontmatter block is present, frontmatterData is an empty object. The same block rules as parseFrontmatter apply: a leading --- block whose YAML is not a mapping (a list, a string, a number) is not frontmatter and is rendered as Markdown.

Frontmatter of any text: parseFrontmatter, parseCommentFrontmatter, stringifyFrontmatter

These functions work on any text, not only Markdown, perform no I/O and are available from both entry points (@push.rocks/smartmarkdown and @push.rocks/smartmarkdown/iso). Each returns { data, content }: data is the parsed YAML mapping, content is the source after the frontmatter block, unchanged.

import { parseFrontmatter, stringifyFrontmatter } from '@push.rocks/smartmarkdown/iso';

const { data, content } = await parseFrontmatter('---\nfileName: index.ts\n---\nexport const x = 1;\n');
// data: { fileName: 'index.ts' }, content: 'export const x = 1;\n'

const withFrontmatter = await stringifyFrontmatter('# Body\n', { title: 'Hello' });
// '---\ntitle: Hello\n---\n# Body\n'

A frontmatter block starts on the first line with ---, ends at the next --- line, and holds a YAML mapping; an empty or comment-only block yields data: {}. Anything else is not frontmatter: data is {} and content is the whole source. That covers an opening --- without a closing one (a YAML file that merely starts with a document marker is left alone) and a complete block whose YAML is a list, string or number. Invalid YAML inside a complete block throws the YAML parse error.

One leading byte order mark (U+FEFF) before the opening --- is skipped and is not part of the returned content; when there is no frontmatter block, content is the whole source including its byte order mark. The Markdown HTML result follows the same rule and never renders a byte order mark.

Files whose syntax has no frontmatter, such as systemd units or shell scripts, can carry it as a leading block of line comments:

import { parseCommentFrontmatter } from '@push.rocks/smartmarkdown/iso';

const unit = `# ---
# name: my-service
# version: 1.0.0
# ---
[Unit]
Description=my service
`;
const { data, content } = await parseCommentFrontmatter(unit, '# ');
// data: { name: 'my-service', version: '1.0.0' }, content: '[Unit]\nDescription=my service\n'

Every line of the block must start with the comment prefix; a line equal to the trimmed prefix (#) counts as an empty line. The block rules of parseFrontmatter apply.

Migration from @push.rocks/smartfm

@push.rocks/smartfm is folded into @push.rocks/smartmarkdown. The new functions are standalone and asynchronous and need no instance; gray-matter is no longer used.

@push.rocks/smartfm @push.rocks/smartmarkdown
import * as smartfm from '@push.rocks/smartfm' import { parseFrontmatter, parseCommentFrontmatter, stringifyFrontmatter } from '@push.rocks/smartmarkdown/iso'
new smartfm.Smartfm({ fmType: 'yaml' }) not needed (fmType was ignored; frontmatter is always YAML, and JSON objects are valid YAML)
smartfmInstance.parse(source) → gray-matter file { data, content, ... } await parseFrontmatter(source) → { data, content }
smartfmInstance.parseFromComments('# ', source) await parseCommentFrontmatter(source, '# ') (argument order swapped)
smartfmInstance.stringify(body, data) await stringifyFrontmatter(body, data)
grayMatter.GrayMatterFile<string> IFrontmatterDocument

Behaviour differences:

  • An opening --- without a closing --- is not frontmatter: the whole source is returned as content. gray-matter consumed the rest of the file as frontmatter.
  • A complete block whose YAML is not a mapping (list, string, number) is not frontmatter: the whole source is returned as content with data: {}. gray-matter returned the non-mapping value as data. Invalid YAML still throws the YAML parse error.
  • parseCommentFrontmatter strips the prefix only from the start of the block lines and returns the rest of the source unchanged. parseFromComments removed every occurrence of the prefix from every line of the file, including the body.
  • A leading byte order mark is removed only together with a frontmatter block. gray-matter removed it from every source, also when there was no frontmatter.
  • stringifyFrontmatter writes content exactly as given and always writes a block, also for empty data (---\n---\n). gray-matter appended a trailing newline and dropped empty blocks.
  • The gray-matter extras matter, excerpt, orig, language, isEmpty and its result cache are gone.

Markdown Features

Markdown parsing uses:

  • remark-parse for Markdown parsing.
  • remark-gfm for GitHub-flavored Markdown.
  • remark-frontmatter for YAML/TOML frontmatter detection.
  • remark-html for HTML output.

HTML-to-Markdown conversion uses:

  • turndown with headingStyle: 'atx'.
  • turndown with codeBlockStyle: 'fenced'.
  • turndown-plugin-gfm for GitHub-flavored Markdown output.

TypeScript and ESM

This package ships as an ES module and includes TypeScript declarations.

import { SmartMarkdown } from '@push.rocks/smartmarkdown';

The package export points to the built ESM entrypoint and is ready for TypeScript, Node.js ESM, and bundler-based frontend projects.

This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.

Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.

Company Information

Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany

For any legal inquiries or further information, please contact us via email at hello@task.vc.

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

S
Description
Enhances Markdown file handling with parsing, conversion, and frontmatter support.
Readme
1.3 MiB
Languages
TypeScript 100%