JSPM

  • Created
  • Published
  • Downloads 82111
  • Score
    100M100P100Q156865F
  • License MIT

Safely render HTML, filters attributes, autowrap text with matchers, render emoji characters, and much more.

Package Exports

  • interweave

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (interweave) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

Interweave v1.2.0

Build Status

Interweave is a robust React library that can...

  • Safely render HTML without using dangerouslySetInnerHTML.
  • Clean HTML attributes using filters.
  • Match and replace text using matchers.
  • Autolink URLs, IPs, emails, and hashtags.
  • Render or replace Emoji characters.
  • And much more!

Requirements

  • React 0.14+
  • IE9+

Installation

Interweave requires React as a peer dependency.

npm install interweave react --save
// Or
yarn add interweave react

Usage

Interweave can primarily be used by the Interweave and Markup components, both of which provide an easy, straight-forward implementation for safely parsing and rendering HTML without using dangerouslySetInnerHTML (Facebook).

The Interweave component has the additional benefit of utilizing filters, matchers, and callbacks.

import Interweave from 'interweave';

<Interweave
    tagName="div"
    content="This string <b>contains</b> HTML."
/>

Props

  • content (string) - The string to render and apply matchers and filters to. Supports HTML.
  • emptyContent (node) - React node to render if no content was rendered.
  • tagName (div | span | p) - The HTML element tag name to wrap the output with. Defaults to "span".
  • filters (Filter[]) - Filters to apply to this local instance.
  • matchers (Matcher[]) - Matchers to apply to this local instance.
  • disableFilters (boolean) - Disables both global and local filters.
  • disableMatchers (boolean) - Disables both global and local matchers.
  • disableLineBreaks (boolean) - Disables automatic line break conversion.
  • noHtml (boolean) - Strips HTML tags from the content string while parsing.
  • onBeforeParse (func) - Callback that fires before parsing. Is passed the source string and must return a string.
  • onAfterParse (func) => Callback that fires after parsing. Is passed an array of strings/elements and must return an array.

Markup

Unlike the Interweave component, the Markup component does not support matchers, filters, or callbacks. This component is preferred when rendering strings that contain HTML is the primary use case.

import { Markup } from 'interweave';

<Markup content="This string <b>contains</b> HTML." />

Props

The Markup component only supports the content, emptyContent, tagName, disableLineBreaks, and noHtml props mentioned previously.

Documentation

Matchers

Matchers are the backbone of Interweave as they allow arbitrary insertion of React elements into strings through the use of regex matching. This feature is quite powerful with many possibilities.

It works by matching patterns within a string, deconstructing it into pieces, and reconstructing it back into an array of strings and React elements, therefore, permitting it to be rendered by React's virtual DOM layer. For example, take the following string "Check out my website, github.com/milesj!", and a UrlMatcher, you'd get the following array.

[
    'Check out my website, ',
    <Url>github.com/milesj</Url>,
    '!',
]

Matchers can be registered globally to apply to all instances of Interweave, or locally, to apply per each instance of Interweave. When adding a matcher, a unique name must be passed to the constructor.

import Interweave from 'interweave';
import EmojiMatcher from 'interweave/matchers/Emoji';

// Global
Interweave.addMatcher(new EmojiMatcher('emoji'));

// Local
<Interweave matchers={[new EmojiMatcher('emoji')]} />

To clear all global matchers, use Interweave.clearMatchers().

Interweave.clearMatchers();

To disable all matchers, global and local, per Interweave instance, pass the disableMatchers prop.

<Interweave disableMatchers />

To disable a single matcher, per instance, you can pass a prop that starts with "no", and ends with the unique name of the matcher (the one passed to the constructor). Using the example above, you can pass a noEmoji prop.

<Interweave noEmoji />

Creating A Matcher

To create a custom matcher, extend the base Matcher class, and define the following methods.

  • match(string) - Used to match a string against a defined regex. This method must return null if no match is found, else it must return an object with a match key and the matched value. Furthermore, any additional keys defined in this object will be passed as props to the created element.
  • replaceWith(match, props) - Returns a React element that replaces the matched content in the string. The match is passed as the 1st argument, and any match props and parent props are passed as the 2nd argument.
  • asTag() - The HTML tag name of the replacement element.
import Matcher from 'interweave/Matcher';

export default class FooMatcher extends Matcher {
    match(string) {
        const matches = string.match(/foo/);

        if (!matches) {
            return null;
        }

        return {
            match: matches[0],
            extraProp: 'foo', // or matches[1], etc
        };
    }

    replaceWith(match, props) {
        return (
            <span {...props}>{match}</span>
        );
    }

    asTag() {
        return 'span';
    }
}

To ease the matching process, there is a doMatch method that handles the null and object building logic. Simply pass it a regex pattern and a callback to build the object.

match(string) {
    return this.doMatch(string, /foo/, matches => ({
        extraProp: 'foo',
    });
}

Rendered Elements

When a match is found, a React element is rendered (from a React component) from either the matcher's replaceWith method, or from a factory. What's a factory you ask? Simply put, it's a function passed to the constructor of a matcher, allowing the rendered element to be customized for built-in or third-party matchers.

To define a factory, simply pass a function to the 3rd argument of a matcher constructor. The factory function receives the same arguments as replaceWith.

new FooMatcher('foo', {}, (match, props) => (
    <span {...props}>{match}</span>
));

Elements returned from replaceWith or the factory must return an HTML element with the same tag name as defined by asTag.

Filters

Filters provide an easy way of cleaning HTML attribute values during the parsing cycle. This is especially useful for src and href attributes.

Filters can be registered globally to apply to all instances of Interweave, or locally, to apply per each instance of Interweave. When adding a filter, the name of the attribute to clean must be passed as the 1st argument to the constructor.

import Interweave from 'interweave';

// Global
Interweave.addFilter(new HrefFilter('href'));

// Local
<Interweave filters={[new HrefFilter('href')]} />

To clear all global filters, use Interweave.clearFilters().

Interweave.clearFilters();

To disable all filters, global and local, per Interweave instance, pass the disableFilters prop.

<Interweave disableFilters />

Creating A Filter

Creating a custom filter is easy. Simply extend the base Filter class and define a filter method. This method will receive the attribute value as the 1st argument, and it must return a string.

import Filter from 'interweave/Filter';

export default class SourceFilter extends Filter {
    filter(value) {
        return value; // Clean attribute value
    }
}

Autolinking

Autolinking is the concept of matching patterns within a string and wrapping the matched result in an anchor link (an <a> tag). This can be achieved with the matchers described below.

Note: The regex patterns in use for autolinking do not conform to the official RFC specifications, as we need to take into account word boundaries, punctuation, and more. Instead, the patterns will do their best to match against the majority common use cases.

URLs, IPs

The UrlMatcher will match most variations of a URL and its segments. This includes the protocol, user and password auth, host, port, path, query, and fragment.

import Interweave from 'interweave';
import UrlMatcher from 'interweave/matchers/Url';

// Global
Interweave.addMatcher(new UrlMatcher('url'));

// Local
<Interweave matchers={[new UrlMatcher('url')]} />

The UrlMatcher does not support IP based hosts as I wanted a clear distinction between supporting these two patterns separately. To support IPs, use the IpMatcher, which will match hosts that conform to a valid IPv4 address (IPv6 not supported). Like the UrlMatcher, all segments are included.

import IpMatcher from 'interweave/matchers/Ip';

If a match is found, a Url element or matcher element will be rendered and passed the following props.

  • children (string) - The entire URL/IP that was matched.
  • urlParts (object)
    • scheme (string) - The protocol. Defaults to "http".
    • auth (string) - The username and password authorization, excluding @.
    • host (string) - The host, domain, or IP address.
    • port (number) - The port number.
    • path (string) - The path.
    • query (string) - The query string.
    • fragment (string) - The hash fragment, including #.

Emails

The EmailMatcher will match an email address and link it using a "mailto:" target.

import Interweave from 'interweave';
import EmailMatcher from 'interweave/matchers/Email';

// Global
Interweave.addMatcher(new EmailMatcher('email'));

// Local
<Interweave matchers={[new EmailMatcher('email')]} />

If a match is found, an Email element or matcher element will be rendered and passed the following props.

  • children (string) - The entire email address that was matched.
  • emailParts (object)
    • username (string) - The username. Found before the @.
    • host (string) - The host or domain. Found after the @.

Hashtags

The HashtagMatcher will match a common hashtag (like Twitter and Instagram) and link to it using a custom URL (passed as a prop). Hashtag matching supports alpha-numeric (a-z0-9), underscore (_), and dash (-) characters, and must start with a #.

Hashtags require a URL to link to, which is defined by the hashtagUrl prop. The URL must declare the following token, {{hashtag}}, which will be replaced by the matched hashtag.

import Interweave from 'interweave';
import HashtagMatcher from 'interweave/matchers/Hashtag';

const hashtagUrl = 'https://twitter.com/hashtag/{{hashtag}}';

// Global
Interweave.configure({ hashtagUrl });
Interweave.addMatcher(new HashtagMatcher('hashtag'));

// Local
<Interweave
    hashtagUrl={hashtagUrl}
    matchers={[new HashtagMatcher('hashtag')]}
/>

If a match is found, a Hashtag element or matcher element will be rendered and passed the following props.

  • children (string) - The entire hashtag that was matched.
  • hashtagName (string) - The hashtag name without #.

Render Emojis

Who loves emojis? Everyone loves emojis. Interweave has built-in support for rendering emoji, either their unicode character or with media, all through the EmojiMatcher. The matcher utilizes EmojiOne for accurate and up-to-date data.

import Interweave from 'interweave';
import EmojiMatcher from 'interweave/matchers/Emoji';

// Global
Interweave.addMatcher(new EmojiMatcher('emoji'));

// Local
<Interweave matchers={[new EmojiMatcher('emoji')]} />

Both unicode literal characters and escape sequences are supported when matching. If a match is found, an Emoji element or matcher element will be rendered and passed the following props.

  • shortName (string) - The shortname when convertShortName is on.
  • unicode (string) - The unicode literal character. Provided for both shortname and unicode matching.

Converting Shortnames

Shortnames provide an easy non-unicode alternative for supporting emoji, and are represented by a word (or two) surrounded by two colons: 👦. A list of all possible shortnames can be found at emoji.codes.

To enable conversion of a shortname to a unicode literal character, pass the convertShortName option to the matcher constructor.

new EmojiMatcher('emoji', { convertShortName: true });

Using SVGs or PNGs

To begin, we must enable conversion of unicode characters to media, by enabling the convertUnicode option. Secondly, enable convertShortName if you want to support shortnames.

new EmojiMatcher('emoji', {
    convertShortName: true,
    convertUnicode: true,
});

Now we need to provide an absolute path to the SVG/PNG file using emojiPath. This path must contain a {{hexcode}} token, which will be replaced by the hexadecimal value of the emoji.

const emojiPath = 'https://example.com/images/emoji/{{hexcode}}.png';

// Global
Interweave.configure({ emojiPath });

// Local
<Interweave emojiPath={emojiPath} />

Both media formats make use of the img tag, and will require an individual file as sprites and icon fonts are not supported. The following resources can be used for downloading SVG/PNG icons.

Note: SVGs require CORS to work, so files will need to be stored locally, or within a CDN under the same domain. Linking to remote SVGs will not work -- use PNGs instead.

CSS Styling

Since SVG/PNG emojis are rendered using the img tag, and dimensions can vary based on the size of the file, we must use CSS to restrict the size. The following styles work rather well, but the end result is up to you.

// Align in the middle of the text
.interweave__emoji {
    display: inline-block;
    vertical-align: middle;
}

// Match the size of the current text
.interweave__emoji img {
    width: 1em;
}

Displaying Unicode Characters

To display native unicode characters as is, pass the renderUnicode option to the matcher constructor. This option will override the rendering of SVGs or PNGs, and works quite well alongside shortname conversion.

new EmojiMatcher('emoji', { renderUnicode: true });

HTML Parsing

Interweave doesn't rely on an HTML parser for rendering HTML safely, instead, it uses the DOM itself. It accomplishes this by using DOMImplementation.createHTMLDocument (MDN), which creates an HTML document in memory, allowing us to easily set markup, aggregate nodes, and generate React elements. This implementation is supported by all modern browsers and IE9+.

DOMImplementation has the added benefit of not requesting resources (images, scripts, etc) until the document has been rendered to the page. This provides an extra layer of security by avoiding possible CSRF and arbitrary code execution.

Furthermore, Interweave manages a whitelist of both HTML tags and attributes, further increasing security, and reducing the risk of XSS and vulnerabilities.

Tag Whitelist

Interweave keeps a mapping of valid HTML tags to parsing configurations. These configurations handle the following rules and processes.

  • Defines the type of rule: allow (render element and children), pass-through (ignore element and render children), deny (ignore both).
  • Defines the type of tag: inline, block, inline-block.
  • Flags whether inline children can be rendered.
  • Flags whether block children can be rendered.
  • Flags whether children of the same tag name can be rendered.
  • Maps the parent tags the current element can render in.
  • Maps the child tags the current element can render.

Lastly, any tag not found in the mapping will be flagged using the rule "deny", and promptly not rendered.

Attribute Whitelist

Interweave takes parsing a step further, by also filtering attribute values on all parsed HTML elements. Like tags, a mapping of valid HTML attributes to parser rules exist. A rule can be one of: allow and cast to string (default), allow and cast to number, allow and cast to boolean, and finally, deny.

Also like the tag whitelist, any attribute not found in the mapping is ignored.

Disabling HTML

The HTML parser cannot be disabled, however, a noHtml boolean prop can be passed to both the Interweave and Markup components. This prop will mark all HTML elements as pass-through, simply rendering text nodes recursively.