@mapbox/jsxtreme-markdown

3.2.0 • Public • Published

@mapbox/jsxtreme-markdown

Transform Markdown with interpolated JS expressions and JSX elements into JSX or React component modules.

This is the low-level, core module that takes one string (Markdown) and converts it to another string (JSX or a React component module).

That low-level focus means this module can be used by a variety of higher-level modules that target specific contexts (Webpack loaders, Browserify transforms, CLIs, etc.).

Installation

npm install @mapbox/jsxtreme-markdown

API

toJsx

jsxtremeMarkdown.toJsx(input, [options])

Transforms jsxtreme-markdown into pure JSX, returning the JSX.

The text runs through a series of steps:

  1. Extract interpolations, replacing them with placeholders that will be handled properly by the Markdown parser.
  2. Run the result through remark to parse the Markdown. (At this stage, you can use any remark plugins) you'd like)
  3. Parsed Markdown is passed into rehype for transformation into HTML. (At this stage, you can use any rehype plugins you'd like.)
  4. Transform the HTML to JSX (with htmltojsx).
  5. Restore the interpolations.
const prettier = require('prettier');
const jsxtremeMarkdown = require('@mapbox/jsxtreme-markdown');

const markdown = `
  # Title

  Here is some **markdown**. *So easy* to write.

  You can interpolate JS expressions like {{ data.number }}
  or {{ dogs.map(d => d.name).join(', ') }}.

  You can also interpolate JSX elements,
  whether {{ <span>inline</span> }} or as a block:

  {{ <div className="fancy-class">
    This is a block.
  </div> }}

  You can even break up JSX interpolation to process more or your text
  as Markdown.

  {{ <div className="fancy-class"}> }}
    This is a **Markdown** paragraph inside the div.

    And this is another.
  {{ </div> }}
`;

const jsx = jsxtremeMarkdown.toJsx(markdown);
console.log(prettier.format(jsx, { parser: 'babel' }));

/*
<div>
  <h1>Title</h1>
  <p>Here is some <strong>markdown</strong>. So <em>easy</em> to write.</p>
  <p>
    You can interpolate JS expressions like {data.number}
    or {dogs.map(d => d.name).join(", ")}.
  </p>
  <p>
    You can also interpolate JSX elements,
    whether <span>inline</span> or as a block:
  </p>
  <div className="fancy-class">
    This is a block.
  </div>
  <p>
    You can even break up JSX interpolation to process more or your text
    as Markdown.
  </p>
  <div className="fancy-class">
    <p>This is a <strong>Markdown</strong> paragraph inside the div.</p>
    <p>And this is another.</p>
  </div>
</div>;
*/

input

Type: string. Required.

Your xtreme Markdown.

options

delimiters

Type: [string, string]. Default: ['{{', '}}'].

Delimiters set off interpolated JS and JSX from the Markdown text. Customize them by passing an array with two strings, one for the opener, one for the closer. For example: ['{%', '%}'].

Note: Do not use delimiters which could clash with JS (${}) or JSX ({}).

escapeDelimiter

Type: string. Default: '#'.

In the rare case that you want to use your delimiters but not for interpolation (e.g. you have code in the text that includes them), you can escape them by prefixing the start delimiter with this character. The escapeDelimiter will be stripped from the output, but the delimiter characters will remain untouched. For example, if you want to include the JSX <div style={{ margin: 10 }} /> in a code block, you would need to escape the double curly brace: <div style=#{{ margin: 10 }} />.

remarkPlugins

Type: Array<Function | [Function, any]>.

The Markdown is parsed by remark. So you can use any remark plugins you'd like (e.g. for linting).

Each item in the array is either a remark plugin function or an array whose first item is the plugin function and second item is plugin options. For example:

{
  remarkPlugins: [
    require('remark-squeeze-paragraphs'),
    [require('remark-lint-emphasis-marker'), '*'],
    [require('remark-toc'), { heading: 'ToC', maxDepth: 2 }],
  ];
}
rehypePlugins

Type: Array<Function | [Function, Object]>.

Parsed Markdown is passed into rehype, at which point it represents HTML nodes. At this stage, you can use any rehype plugins you'd like (e.g. for syntax highlighting).

Each item in the array is either a remark plugin function or an array whose first item is the plugin function and second item is plugin options. For example:

{
  remarkPlugins: [
    require('rehype-picture),
    [require('rehype-prism'), { ignoreMissing: true }]
  ]
}

toComponentModule

jsxtremeMarkdown.toComponentModule(input, [options])

Uses toJsx, above, to transform Markdown to JSX. Also parses front matter. Returns a JS string representing a React component module that wraps this content.

The JSX is plugged into a template to produce the React component module. A default template is provided that produces the output exemplified below. You can also provide your own template to fit your own needs and preferences.

const jsxtremeMarkdown = require('@mapbox/jsxtreme-markdown');

const markdown = `
  ---
  title: Everything is ok
  quantity: 834
  wrapper: "../wrapper.js",
  prependJs:
    - "const Timer = require('./timer')"
    - "import { Watcher } from './watcher'"
  ---

  # {{ frontMatter.title }}

  Some introductory text. The quantity is {{ frontMatter.quantity }}

  {{ <Watcher /> }}

  This paragraph includes a {{ <Timer /> }}.

  This component also accepts a "foo" prop: {{ props.foo }}
`;

const js = jsxtremeMarkdown.toComponentModule(markdown);
console.log(js);

/*
import React from "react";
import Timer from "./timer";
import { Watcher } from "./watcher";
import Wrapper from "../wrapper";

const frontMatter = {
  title: "Everything is ok",
  quantity: 834
};

export default class MarkdownReact extends React.PureComponent {
  render() {
    const props = this.props;
    return (
      <Wrapper {...props} frontMatter={frontMatter}>
        <div>
          <h1>{frontMatter.title}</h1>
          <p>Some introductory text. The quantity is {frontMatter.quantity}</p>
          <Watcher />
          <p>This paragraph includes a <Timer />.</p>
          <p>This component also accepts a "foo" prop: {props.foo}</p>
        </div>
      </Wrapper>
    );
  }
}
*/

input

Type: string. Required.

Your xtreme Markdown.

options

You can pass any of the options for toJsx, documented above. Also the following:

wrapper

Type: string.

The path to a wrapper component. This value can be overridden document-by-document by setting wrapper in the front matter of the Markdown. The wrapper component must be exported with module.exports or export default, not a named ES2015 export.

The wrapper component will receive the following props:

  • All the props passed to the component at runtime.
  • frontMatter: The parsed front matter.
  • children: The JSX content generated from your source Markdown.
prependJs

Type: Array<string>.

An array of lines of JS code that will be prepended to the top of the JavaScript. The typical use-case is to require or import modules that will be used by interpolated JS and JSX. This value can be added to document-by-document by setting prependJs in the front matter of specific documents.

template

Type: (data: Object) => string.

An alternative template function.

Receives as its argument a data object and must return a string. Look to the default template as an example. The data object includes the following:

  • wrapper: The value of the wrapper option, above.
  • prependJs: The value of the prependJs option, above.
  • name: The value of the name option, above, converted to PascalCase.
  • frontMatter: The parsed front matter.
  • jsx: The JSX string generated from your source Markdown.
headings

Type: boolean. Default: false.

The primary use case for the headings option is to build a table of contents in your wrapper component.

If true, the following will happen:

  • Every heading element in the Markdown will have an id attribute whose value is the element's slugified text.
  • The module's frontMatter object will be augmented with a headings array. Each item in the array is an object with text, slug, and level properties.

For example:

const jsxtremeMarkdown = require('@mapbox/jsxtreme-markdown');

const markdown = `
  # One

  Text.

  ## Two

  Some more text.

  ### Third-level heading

  Yet more.

  ## Two

  A section with a duplicate title.
`;

const js = jsxtremeMarkdown.toComponentModule(markdown);
console.log(js);

/*
import React from "react";

const frontMatter = {
  headings: [
    {
      text: "One",
      slug: "one",
      level: 1
    },
    {
      text: "Two",
      slug: "two",
      level: 2
    },
    {
      text: "Third-level heading",
      slug: "third-level-heading",
      level: 3
    },
    {
      text: "Two",
      slug: "two-1",
      level: 2
    }
  ]
};

export default class MarkdownReact extends React.PureComponent {
  render() {
    const props = this.props;
    return (
      <div>
        <h1 id="one">One</h1>
        <p>Text.</p>
        <h2 id="two">Two</h2>
        <p>Some more text.</p>
        <h3 id="third-level-heading">Third-level heading</h3>
        <p>Yet more.</p>
        <h2 id="two-1">Two</h2>
        <p>A section with a duplicate title.</p>
      </div>
    );
  }
}
*/

A couple of things to keep in mind when using this option:

  • Do not use interpolation in your heading text!
  • Slugs are generated with github-slugger, so should match the slugging patterns found in rendered Markdown files on GitHub.
precompile

Type: boolean. Default: false.

If true, the returned string will be compiled with Babel (using @babel/preset-env and @babel/preset-react).

name

Type: string. Default: MarkdownReact.

The name of the component class that will be generated.

The default template

For the default template, there are two special front matter properties that Markdown documents can use:

  • wrapper: Path to a wrapper component. This can be set outside the front matter with the wrapper option, above. See those docs for more details.
  • prependJs: See the the prependJs option, above. In a document's front matter, this property will add lines to the value of that option, for that specific module.

Versions

Current Tags

  • Version
    Downloads (Last 7 Days)
    • Tag
  • 3.2.0
    68
    • latest

Version History

Package Sidebar

Install

npm i @mapbox/jsxtreme-markdown

Weekly Downloads

59

Version

3.2.0

License

MIT

Unpacked Size

152 kB

Total Files

70

Last publish

Collaborators

  • mbx-npm-ci-production
  • mbx-npm-ci-staging
  • mbx-npm-advanced-actions-production
  • mbx-npm-advanced-actions-staging
  • mbx-npm-09-production
  • mbx-npm-08-production
  • mbx-npm-07-production
  • mbx-npm-06-production
  • mbx-npm-05-production
  • mbx-npm-04-production
  • mbx-npm-03-production
  • mbx-npm-02-production
  • mbx-npm-01-production
  • mbx-npm-02-staging
  • mapbox-npm-01
  • mapbox-npm-02
  • mapbox-npm-07
  • mapbox-npm-03
  • mapbox-npm-04
  • mapbox-npm-09
  • mapbox-npm-05
  • mapbox-npm-06
  • mapbox-npm-08
  • mapbox-npm-advanced-actions
  • mapbox-npm-ci
  • mapbox-npm
  • mapbox-admin
  • mapbox-machine-user