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
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 returnnull
if no match is found, else it must return an object with amatch
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 whenconvertShortName
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.