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

markdown-academic

v0.1.0

Published

Academic writing in Markdown - Math, citations, cross-references, and more

Readme

markdown-academic

Academic writing in Markdown - Math, citations, cross-references, and more.

This package provides JavaScript/TypeScript bindings (via WebAssembly) that work in both Node.js and browser environments.

Installation

npm / pnpm / yarn

npm install markdown-academic
# or
pnpm add markdown-academic
# or
yarn add markdown-academic

CDN (No Build Required)

Use directly in the browser via CDN - no bundler needed:

<!-- Using esm.sh (recommended for ESM) -->
<script type="module">
import { init, render } from 'https://esm.sh/markdown-academic';

await init();
const html = render('# Hello $E=mc^2$');
</script>

<!-- Using jsDelivr -->
<script type="module">
import { init, render } from 'https://cdn.jsdelivr.net/npm/markdown-academic/+esm';

await init();
const html = render('# Hello $E=mc^2$');
</script>

<!-- Using unpkg -->
<script type="module">
import { init, render } from 'https://unpkg.com/markdown-academic?module';

await init();
const html = render('# Hello $E=mc^2$');
</script>

Quick Start

import { init, render, MathBackend } from 'markdown-academic';

// Initialize the WASM module (required once)
await init();

// Render markdown to HTML
const html = render('# Hello $E=mc^2$');

// With options
const fullHtml = render(source, {
  standalone: true,
  mathBackend: MathBackend.KaTeX,
  title: 'My Document'
});

API Reference

init(wasmPath?: string): Promise<void>

Initialize the WASM module. Must be called before using any other functions.

await init();
// or with custom WASM path
await init('/path/to/markdown_academic_bg.wasm');

render(input: string, options?: RenderOptions): string

Render markdown-academic source to HTML.

// Simple usage
const html = render('# Hello');

// With options
const html = render(source, {
  standalone: true,      // Generate complete HTML document
  mathBackend: 'katex',  // 'katex', 'mathjax', or 'mathml'
  title: 'My Document',  // Document title
  customCss: 'body { }', // Custom CSS
  includeToc: true,      // Include table of contents
  classPrefix: 'mda',    // CSS class prefix
  strictMode: false      // Error on unresolved refs
});

parseDocument(input: string): ParsedDocument

Parse a document and get structured information.

const doc = parseDocument(source);

console.log(doc.metadata.title);
console.log(doc.metadata.authors);
console.log(doc.statistics.wordCount);
console.log(doc.labels); // All cross-reference labels

validate(input: string): ValidationResult

Validate a document without rendering.

const result = validate(source);

if (!result.valid) {
  console.error('Errors:', result.errors);
  console.warn('Warnings:', result.warnings);
}

parseToJson(input: string): string

Get the full parsed document as JSON (useful for debugging).

const json = parseToJson(source);
console.log(JSON.parse(json));

getVersion(): string

Get the library version.

console.log(getVersion()); // "0.1.0"

hasFeature(feature: Feature): boolean

Check if a feature is supported.

if (hasFeature('mathml')) {
  // MathML is available
}

RenderOptions Class

For more control, use the RenderOptions class:

import { RenderOptions, MathBackend } from 'markdown-academic';

const options = new RenderOptions();
options
  .setStandalone(true)
  .setMathBackend(MathBackend.KaTeX)
  .setTitle('My Document')
  .setCustomCss('body { max-width: 800px; }')
  .setIncludeToc(true);

const html = render(source, options);

Types

MathBackend

enum MathBackend {
  KaTeX = 'katex',    // Fast, client-side (default)
  MathJax = 'mathjax', // Full LaTeX support
  MathML = 'mathml'    // Native browser rendering
}

ParsedDocument

interface ParsedDocument {
  metadata: {
    title?: string;
    subtitle?: string;
    authors: string[];
    date?: string;
    keywords: string[];
    institution?: string;
    macros: string[];
    bibliographyPath?: string;
  };
  blocks: BlockInfo[];
  labels: LabelInfo[];
  statistics: {
    blockCount: number;
    headingCount: number;
    equationCount: number;
    citationCount: number;
    figureCount: number;
    tableCount: number;
    footnoteCount: number;
    wordCount: number;
  };
}

Browser Usage

ES Modules

<script type="module">
import { init, render } from 'markdown-academic';

await init();

const source = document.getElementById('editor').value;
const html = render(source, { standalone: false });
document.getElementById('preview').innerHTML = html;
</script>

With a Bundler (Vite, Webpack, etc.)

import { init, render } from 'markdown-academic';

async function setup() {
  await init();
  // Ready to use
}

Node.js Usage

ESM

import { init, render } from 'markdown-academic';

await init();
const html = render('# Hello');
console.log(html);

CommonJS

const { init, render } = require('markdown-academic');

async function main() {
  await init();
  const html = render('# Hello');
  console.log(html);
}

main();

Building from Source

Prerequisites:

  • Rust toolchain
  • wasm-pack (cargo install wasm-pack)
  • Node.js 18+
  • pnpm (recommended)
# Clone the repository
git clone https://github.com/quinnjr/markdown-academic.git
cd markdown-academic/wasm

# Install dependencies
pnpm install

# Build (compiles Rust to WASM and bundles TypeScript)
pnpm run build

Example: Live Preview Editor (CDN)

This example works without any build step - just save as an HTML file and open in a browser:

<!DOCTYPE html>
<html>
<head>
  <title>MDA Editor</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css">
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js"></script>
</head>
<body>
  <div style="display: flex; gap: 20px;">
    <textarea id="editor" style="width: 50%; height: 400px;">
# Introduction {#sec:intro}

The equation $E = mc^2$ demonstrates mass-energy equivalence.

See @sec:intro for more.
    </textarea>
    <div id="preview" style="width: 50%; height: 400px; overflow: auto;"></div>
  </div>

  <script type="module">
    // Load directly from CDN - no npm install needed!
    import { init, render } from 'https://esm.sh/markdown-academic';

    await init();

    const editor = document.getElementById('editor');
    const preview = document.getElementById('preview');

    function update() {
      try {
        preview.innerHTML = render(editor.value);
        renderMathInElement(preview);
      } catch (e) {
        preview.innerHTML = `<pre style="color: red;">${e.message}</pre>`;
      }
    }

    editor.addEventListener('input', update);
    update();
  </script>
</body>
</html>

License

MIT License - see LICENSE

Author

Joseph R. Quinn [email protected]