Skip to content

ipikuka/plugins

Repository files navigation

@ipikuka/plugins

NPM version NPM downloads Build codecov type-coverage typescript License

This package is a collection of unified (remark, rehype and recma) plugins and rehype handlers for markdown / MDX that I used in my many projects.

unified is a project that transforms content with abstract syntax trees (ASTs) using the new parser micromark.

remark adds support for markdown to unified. mdast is the Markdown Abstract Syntax Tree (AST) which is a specification for representing markdown in a syntax tree.

rehype is a tool that transforms HTML with plugins. hast stands for HTML Abstract Syntax Tree (HAST) that rehype uses.

recma adds support for producing a javascript code by transforming esast which stands for Ecma Script Abstract Syntax Tree (AST) that is used in production of compiled source for MDX.

This package provides remarkPlugins, rehypePlugins, recmaPlugins, and remarkRehypeOptions for @mdx-js/mdx and related projects like next-mdx-remote and next-mdx-remote-client.

When should I use this?

If you don't want to install and configure any specific remark, rehype and recma plugin; @ipikuka/plugins provides you a plugin list that is opinionated and well tested.

It also helps creating table of contents (TOC) for markdown/mdx content out of the box via remark-flexible-toc.

The remark plugins that exposed by @ipikuka/plugins:
(exactly in specific order below)

  • remark-fix-guillemets
  • remark-smartypants
  • remark-flexible-markers
  • remark-ins
  • remark-gfm
  • remark-textr (with custom textr-plugins)
  • remark-definition-list
  • remark-flexible-paragraphs
  • remark-supersub
  • remark-gemoji
  • remark-emoji
  • remark-flexible-containers
  • remark-flexible-code-titles
  • remark-flexible-toc

The rehype plugins that exposed by @ipikuka/plugins:
(exactly in specific order below)

  • rehype-raw
  • rehype-slug
  • rehype-autolink-headings
  • rehype-prism-plus
  • rehype-pre-language

The recma plugins (only for MDX content) that exposed by @ipikuka/plugins:
(exactly in specific order below)

  • recma-mdx-escape-missing-components
  • recma-mdx-change-props

The rehype handlers that exposed by @ipikuka/plugins:\

  • defListHastHandlers from "remark-definition-list"
  • a custom html handler for only markdown content

Installation

This package is suitable for ESM only. In Node.js (version 16+), install with npm:

npm install @ipikula/plugins

or

yarn add @ipikula/plugins

Usage

Let's create a wrapper for serialize function of next-mdx-remote-client and use @ipikua/plugins inside.

// serialize.ts

import { serialize as serialize_ } from "next-mdx-remote-client/serialize";
import type { SerializeResult, SerializeProps } from "next-mdx-remote-client/serialize";
import { plugins, prepare, type TocItem } from "@ipikua/plugins";

export async function serialize<
  TFrontmatter extends Record<string, unknown> = Record<string, unknown>,
  TScope extends Record<string, unknown> = Record<string, unknown>,
>({
  source,
  options,
}: SerializeProps<TScope>): Promise<SerializeResult<TFrontmatter, TScope & { toc?: TocItem[] }>> {
  const { mdxOptions, ...rest } = options || {};

  const format_ = mdxOptions?.format;
  const format = format_ === "md" || format_ === "mdx" ? format_ : "mdx";
  const processedSource = format === "mdx" ? prepare(source) : source;

  return await serialize_<TFrontmatter, TScope>({
    source: processedSource,
    options: {
      mdxOptions: {
        ...mdxOptions,
        ...plugins({ format }),
      },
      vfileDataIntoScope: "toc",
      ...rest,
    },
  });
};

Let's create another wrapper for serialize function of next-mdx-remote and use @ipikua/plugins inside.

// serialize.ts

import { serialize as serialize_, type SerializeOptions } from "next-mdx-remote/serialize";
import { plugins, prepare, type TocItem } from "@ipikua/plugins";
import { type Compatible } from "vfile";

export async function serialize<
  TFrontmatter extends Record<string, unknown> = Record<string, unknown>,
  TScope extends Record<string, unknown> = Record<string, unknown>,
>(
  source: Compatible,
  { mdxOptions, parseFrontmatter, scope }: SerializeOptions = {},
): Promise<MDXRemoteSerializeResult<TScope & { toc?: TocItem[] }, TFrontmatter>> {
  const toc: TocItem[] = [];

  const { format: format_, ...rest } = mdxOptions || {};
  const format = format_ === "md" || format_ === "mdx" ? format_ : "mdx";
  const processedSource = format === "mdx" ? prepare(source) : source;

  return await serialize_<TScope & { toc?: TocItem[] }, TFrontmatter>(processedSource, {
    parseFrontmatter,
    scope: { ...scope, toc },
    mdxOptions: {
      format,
      ...rest,
      ...plugins({ format, toc }),
    },
  });
};

You can use the serialize wrappers in pages router of nextjs applications.

Note

I will try to provide a complete example nextjs applications later.

Thanks to @ipikuka/plugins, the markdown/mdx content will support table of contents, containers, markers, aligned paragraphs, gfm syntax (tables, strikethrough, task lists, autolinks etc.), inserted texts, highlighted code fences, code titles, autolink for headers, definition lists etc. in addition to standard markdown syntax like bold texts, italic texts, lists, blockquotes, headings etc.

Without @ipikua/plugins the result would be a standart markdown result with no containers, no markers, no gfm syntax, no inserted texts, no highlighted code fences etc.

Options

type PluginOptions = {
  format?: CompileOptions["format"];
  toc?: TocItem[];
};

format

It is "md" | "mdx" | "detect" | null | undefined option to adjust remark plugins and whether or not to employ recma plugins.

It is optional, and default is mdx.

toc

It is TocItem[] option to compose a table of content by remark-flexible-toc.

It is optional and have no default value.

If you want to have a table of content and supplied into the scope, I advise you use the option toc if you use next-mdx-remote, but you don't need it for next-mdx-remote-client thanks to the option vfileDataIntoScope: "toc".

Examples:

Example with @mdx-js/mdx

import { compile } from "@mdx-js/mdx";
import { plugins, type TocItem } from "@ipikua/plugins";

// ...
const toc: TocItem[] = []; // if you don't need a table of content then you can omit it.

const compiledSource = await compile(source, {
  ...plugins({ format: "md", toc }),
})

console.log(toc); // now it has table of contents

// ...

Example with next-mdx-remote-client

import { serialize } from "next-mdx-remote-client/serialize";
import { plugins } from "@ipikua/plugins";

// ...
const mdxSource = await serialize<TFrontmatter, TScope>({
  source,
  options: {
    mdxOptions: {
      ...plugins({ format: "md" }),
    },
    parseFrontmatter: true,
    scope: {},
    vfileDataIntoScope: "toc", // it will ensure the scope has `toc`
  },
});

console.log(mdxSource.scope.toc); // now it has table of contents

// ...

Example with next-mdx-remote

import { serialize } from "next-mdx-remote/serialize";
import { plugins, type TocItem } from "@ipikua/plugins";

// ...
const toc: TocItem[] = []; // if you don't need a table of content then you can omit it.

const mdxSource = await serialize<TScope, TFrontmatter>(
  source,
  {
    mdxOptions: {
      ...plugins({ format: "md", toc }),
    },
    parseFrontmatter: true,
    scope: { toc },
  },
);

console.log(mdxSource.scope.toc); // now it has table of contents

// ...

Utils

The package exposes one utility function which is called prepare.

prepare(source: Compatible)

It is for MDX source (not markdown) to correct breaklines to <br/>, horizontal lines to <hr/>, guillements to « » and or equals signs to and . The prepare function accepts Compatible (see vfile) but check if it is string, otherwise do nothing.

The reason for having prepare function is that remark parser for markdown content and mdx parser for mdx content are different.

Syntax tree

The plugins modifies the mdast (Markdown abstract syntax tree), the hast (HTML abstract syntax tree) and the esast (EcmaScript abstract syntax tree).

Types

This package is fully typed with TypeScript.

The package exports the type PluginOptions, CompileOptions, TocItem.

Compatibility

The plugins that are provided by this package work with unified version 6+, MDX version 2+, next-mdx-remote version canary, next-mdx-remote-client version 1+.

Security

Use of some rehype plugins involves hast, but doesn't lead to cross-site scripting (XSS) attacks.

My Plugins

I like to contribute the Unified / Remark / MDX ecosystem, so I recommend you to have a look my plugins.

My Remark Plugins

My Rehype Plugins

  • rehype-pre-language – Rehype plugin to add language information as a property to pre element

My Recma Plugins

  • recma-mdx-escape-missing-components – Recma plugin to set the default value () => null for the Components in MDX in case of missing or not provided so as not to throw an error
  • recma-mdx-change-props – Recma plugin to change the props parameter into the _props in the function _createMdxContent(props) {/* */} in the compiled source in order to be able to use {props.foo} like expressions. It is useful for the next-mdx-remote or next-mdx-remote-client users in nextjs applications.

License

MIT License © ipikuka

Keywords

🟩 unified 🟩 remark 🟩 remark plugin 🟩 mdast 🟩 rehype 🟩 rehype plugin 🟩 hast 🟩 recma 🟩 recma plugin 🟩 esast 🟩 markdown 🟩 mdx