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

remark-replace-delimited

v0.2.0

Published

remark plugin to replace arbitrary delimited text with HTML

Readme

remark-replace-delimited

This is a unified (remark) plugin that replaces arbitrary delimited text with HTML. It enables the creation of new Markdown-style delimiter runs and converts them into valid HTML.

This plugin was created to support Obsidian-specific Markdown (e.g., ==highlights== and [[internal wikilinks]]) in non-Obsidian use cases (e.g., Astro), as well as other custom delimiters as defined by the developer.

Examples

These examples are only for demonstration purposes. Please adhere to common Markdown practices when adding your own delimiter run replacements.

Highlighting

==Finish writing <b>this</b> article.==

...run through:

remarkReplaceDelimited({
  beginDelimiter: '==',
  beginReplacement: '<mark>',
  endReplacement: '</mark>',
})(tree);

...becomes:

<mark>Finish writing <b>this</b> article.</mark>

Internal wikilinks

Refer to the main article on [[Encyclopedia Britannica]].

...run through:

import escapeHtml from 'escape-html';

remarkReplaceDelimited({
  beginDelimiter: '[[',
  endDelimiter: ']]',
  beginReplacement: (value) => {
    value = escapeHtml(value).toLowerCase().replace(/\s/g, '-');
    return `<a href="/${value}">`;
  },
  endReplacement: '</a>',
})(tree);

...becomes:

Refer to the main article on <a href="/encyclopedia-britannica">Encyclopedia Britannica</a>.

YouTube video as JSON

[yt[{"title": "Developers", "id": "Vhh_GeBPOhs"}]]

...run through:

import escapeHtml from 'escape-html';

remarkReplaceDelimited({
  beginDelimiter: /\[([a-z]+)\[/,
  endDelimiter: ']]',
  replacement: (value, beginMatch, _endMatch) => {
    if (beginMatch[1] !== 'yt') { // Match first regexp group
      throw new EvalError(
        `Delimiter run '${beginMatch}' replacement not implemented`
      );
    }
    const data = JSON.parse(value);
    return (
      `<iframe src="https://www.youtube.com/embed/${escapeHtml(data.id)}" title="'${escapeHtml(data.title)}' on YouTube"></iframe>`
    );
  }
});

...becomes:

<iframe src="https://www.youtube.com/embed/Vhh_GeBPOhs" title="'Developers' on YouTube"></iframe>

Usage

[!WARNING] This package can result in cross-site scripting (XSS) attacks, as replacement values are inserted directly into the document tree as HTML nodes. Ensure that unknown strings are sanitised before returning them in replacement values (e.g., using escape-html).

This package has a single default export, remarkReplaceDelimited(), which accepts an options object containing the below properties. A new "type": "html" node is created for each replacement, and their values are set to the replacement value.

| Name | Type | Description | | - | - | - | | beginDelimiter | string or RegExp | The delimiter that indicates the start of an affected span of text. | | endDelimiter? | string or RegExp | The delimiter that indicates the end of an affected span of text. If undefined, will be beginDelimiter. | | includeHtmlContent? | boolean | Whether to include text nodes nested within HTML elements when searching for delimiters. If undefined, defaults to false to mirror Markdown parsing. | | beginReplacement? | ReplacementValue | The text to replace beginDelimiter with. | | endReplacement? | ReplacementValue | The text to replace endDelimiter with. | | replacement? | ReplacementValue | The text to replace the entire delimiter run with. Cannot be used if beginReplacement or endReplacement are in use. |

ReplacementValue is a type alias for either a raw string value, or a function that takes the following arguments:

| Name | Type | Description | | - | - | - | | value | string | The inner text of the delimiter run. | | beginMatch | string or RegExpExecArray | The match object for the beginDelimiter. If beginDelimiter is a RegExp, this will be a RegExpExecArray. | | endMatch | string or RegExpExecArray | The match object for the endDelimiter. If endDelimiter is a RegExp, this will be a RegExpExecArray. |

remark, Astro and others do not support tree transformer functions being passed in directly, as they expect parameterless functions that return transformers. Therefore, this plugin must be passed in using a wrapper function, as shown below.

To use this plugin in Astro, update astro.config.mjs like so:

export default defineConfig({
  markdown: {
    processor: unified({
      remarkPlugins: [
        () =>
          remarkReplaceDelimited({
            // Add your options here
          }),
      ],
    }),
  },
});

Specification

  • Single regular expression replacement is not supported, due to the need to retain node awareness
    • Text to replace must be delimited on both sides, therefore, blank delimiter matches are not supported
  • Delimiter runs are only identified in headings, paragraphs and table cells (GitHub Flavoured Markdown only), and not within code or HTML, as in Markdown
    • Within-HTML identification is optional for the sake of readability
  • All but one replacement can be undefined to support icon image insertion etc.
  • Replacements can be functions to support using inner text, enabling internal wikilink syntax etc.
  • Whole string replacement is supported to allow for removing metadata etc.
    • Delimiter-only replacement remains in order to support nested replacement
  • Inner text is passed to replacement functions as a concatenated values string
    • Passing the tree introduces unneeded complexity
  • Replacements are strings that are converted to HTML nodes regardless of their content
    • Requiring replacement nodes introduces unneeded complexity, developers should instead follow XSS best practices
  • Existing node positions are updated to exclude removed characters
  • Created nodes have no position set, as they are not in the original document

See also

  • remark-directive - Convert generic directives (e.g. :cite[smith04], ::youtube[Video of a cat in a box]{v=01ab2cd3efg}, etc.) to valid HTML
  • remark-math - Convert LaTeX math blocks to valid HTML using KaTeX or MathJax

Contributing

To start contributing, fork this repository. Execute npm run test to ensure no changes are broken. Pull requests are welcome.

License