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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@acdh-oeaw/mdx-lib

v0.1.3

Published

utilities and plugins for [`mdx`](https://mdxjs.com).

Downloads

142

Readme

mdx lib

utilities and plugins for mdx.

how to install

npm i @acdh-oeaw/mdx-lib

how to use

configure types for supported locales and jsx:

// ./types/mdx.d.ts

// import type * as runtime from "astro/jsx-runtime";
import type * as runtime from "react/jsx-runtime";

declare module "mdx/types" {
	namespace JSX {
		type Element = runtime.JSX.Element;
		type ElementClass = runtime.JSX.ElementClass;
		type IntrinsicElements = runtime.JSX.IntrinsicElements;
	}
}

declare module "@acdh-ch/mdx-lib" {
	export interface MdxConfig {
		locales: "de" | "en";
	}
}

provide component mappings (in next.js the file location has to be mdx-components.tsx at the project root):

// ./mdx-components.ts

import { Link } from "@/components/link";

const components = {
	a: Link,
	/** ... */
};

declare global {
	type MDXProvidedComponents = typeof components;
}

export function useMDXComponents(): MDXProvidedComponents {
	return components;
}

create locale aware mdx compilers:

// ./lib/mdx/compile-mdx.ts

import { createMdxProcessors, run } from "@acdh-ch/mdx-lib";
// import * as runtime from "astro/jsx-runtime";
import * as runtime from "react/jsx-runtime";

const createProcessor = createMdxProcessors((locale) => {
	return {
		/** When using `astro` instead of `react`. */
		// elementAttributeNameCase: "html",
		// jsxImportSource: "astro",

		remarkPlugins: [],
		remarkRehypeOptions: {},
		rehypePlugins: [],
	};
});

export async function compileMdx(content: string, baseUrl: URL, locale: string) {
	const processor = await createProcessor(locale);
	const compiled = await processor.process(content);
	return run(compiled, { ...runtime, baseUrl, useMDXComponents });
}

note that in astro projects, you need to use the astro jsx runtime (astro/jsx-runtime), and set elementAttributeNameCase to "html".

plugins

with-custom-heading-ids

rehype plugin which finds <HeadingId id="abc" /> mdx components, and applies the id attribute to a parent heading element.

example:

## This is a heading <HeadingId id="my-custom-heading" />

when not using any other plugins, this would be transformed to a regular <h2> element. when using the rehype-slug plugin, this would add an auto-generated id attribute: <h2 id="this-is-a-heading">. when using this plugin, you can add custom ids to the heading: <h2 id="my-custom-heading">. the <HeadingId> component will be removed from the generated output.

with-footnotes

remark plugin which finds inline <Footnote> mdx components, and transforms them to markdown footnotes, i.e. footnoteReference and footnoteDefinition mdast nodes (requires remark-gfm).

example:

This is an important sentence.<Footnote>Found it on the internet.</Footnote>

will be transformed to:

<p>
	This is an important sentence.<sup
		><a
			href="#user-content-fn-1"
			id="user-content-fnref-1"
			data-footnote-ref
			aria-describedby="footnote-label"
			>1</a
		></sup
	>.
</p>
<section data-footnotes class="footnotes">
	<h2 class="sr-only" id="footnote-label">Footnotes</h2>
	<ol>
		<li id="user-content-fn-1">
			<p>
				Found it on the internet.
				<a
					href="#user-content-fnref-1"
					data-footnote-backref=""
					aria-label="Back to reference 1"
					class="data-footnote-backref"
					>↩</a
				>
			</p>
		</li>
	</ol>
</section>

The h2 text content and aria-labels for generated elements can be controlled by setting remarkRehypeOptions.

with-iframe-titles

rehype plugin which applies the text content of a component to its title attribute. this is meant to be used with components which render an <iframe>. accepts a components option listing the names of components to handle.

example:

<Embed src="https://example.com/iframe">This is **the** title.</Embed>

will be transformed to:

<iframe src="https://example.com/iframe" title="This is the title." />

with-image-imports

rehype plugin which transforms the src attribute of <img> elements and configured mdx components into esm imports, because most javascript frameworks provide image optimisation features which integrate with a bundler and work via import statements.

example:

<img src="./first-image.png" />

<Figure src="/second-image.png">This is the image caption.</Figure>

will be transformed to:

import __image1__ from "./first-image.png";
import __image2__ from "/home/stefan/my-project/public/second-image.png";

<img src={__image1__} />

<Figure src={__image2__}>This is the image caption.</Figure>

note that paths starting with "/" will be expanded to absolute paths using the publicPath config option (defaults to "/public/"). for relative paths starting with "./" or "../", you need to provide a baseUrl to the mdx compiler.

also note that you need a bundler to handle the image imports, so this requires outputFormat: "program".

don't forget to map img elements to a custom component which can handle objects as src prop, e.g.:

// ./mdx-components.ts

import Image from "next/image";

const components = {
	img: Image,
};

export function useMDXComponents() {
	return components;
}

with-table-of-contents

rehype plugin which generates a table of contents, and provides it via vfile.data.tableOfContents, as well as a tableOfContents named export. additionally, it provides the table of contents data to any TableOfContents mdx component.

const vfile = await processor.process(content);
console.log(vfile.data.tableOfContents);
const { default: Content, tableOfContents } = await run(vfile, { ...runtime });

example using all provided plugins

// ./lib/mdx/compile-mdx.ts

import {
	createMdxCompiler,
	createMdxProcessors,
	typographyConfig,
	withCustomHeadingIds,
	withFootnotes,
	withIframeTitles,
	// withImageImports,
	withImageSizes,
	withTableOfContents,
} from "@acdh-ch/mdx-lib";
import withHeadingIds from "rehype-slug";
import withGfm from "remark-gfm";
import withTypographicQuotes from "remark-smartypants";
import * as runtime from "react/jsx-runtime";

import { useMDXComponents } from "../../mdx-components";

const createProcessor = createMdxProcessors((locale) => {
	return {
		remarkPlugins: [withGfm, [withTypographicQuotes, typographyConfig[locale]], withFootnotes],
		remarkRehypeOptions: {
			footnoteBackLabel(referenceIndex, rereferenceIndex) {
				return t("footnoteBackLabel", {
					reference: referenceIndex + 1 + (rereferenceIndex > 1 ? "-" + rereferenceIndex : ""),
				});
			},
			footnoteLabel: t("footnoteLabel"),
		},
		rehypePlugins: [
			withCustomHeadingIds,
			withHeadingIds,
			[withIframeTitles, { components: ["Embed", "Iframe", "Video"] }],
			// [withImageImports, { components: ["Figure"] }],
			[withImageSizes, { components: ["Figure"] }],
			withTableOfContents,
		],
	};
});

export const compileMdx = createMdxCompiler(createProcessor, runtime, useMDXComponents);