Git Product home page Git Product logo

htmr's Introduction

htmr Actions Status bundle size

Simple and lightweight (< 2kB) HTML string to react element conversion library

Install

$ yarn add htmr

# or

$ npm install htmr --save

Usage

Use the default export, and pass HTML string.

import React from 'react';
import htmr from 'htmr';

function HTMLComponent() {
  return htmr('<p>No more dangerouslySetInnerHTML</p>');
}

The API also accepts second argument options containing few optional fields. Below are their default values:

const options = {
  transform: {},
  preserveAttributes: [],
  dangerouslySetChildren: ['style'],
};
htmr(html, options);

transform

transform accepts key value pairs, that will be used to transforms node (key) to custom component (value). You can use it to render specific tag name with custom component. For example: component with predefined styles like styled-components.

import React from 'react';
import htmr from 'htmr';
import styled from 'styled-components';

const Paragraph = styled.p`
  font-family: Helvetica, Arial, sans-serif;
  line-height: 1.5;
`;

const transform = {
  p: Paragraph,
  // you can also pass string for native DOM node
  a: 'span',
};

function TransformedHTMLComponent() {
  // will return <Paragraph><span>{'Custom component'}</span></Paragraph>
  return htmr('<p><a>Custom component</a></p>', { transform });
}

You can also provide default transform using underscore _ as property name.

This can be useful if you want to do string preprocessing (like removing all whitespace), or rendering HTML as native view in react-native:

import React from 'react';
import { Text, View } from 'react-native';

const transform = {
  div: View,
  _: (node, props, children) => {
    // react-native can't render string without <Text> component
    // we can test text node by checking component props, text node won't have them
    if (typeof props === 'undefined') {
      // use `key` because it's possible that <Text> is rendered
      // inside array as sibling
      return <Text key={node}>{node}</Text>;
    }

    // render unknown tag using <View>
    // ideally you also filter valid props to <View />
    return <View {...props}>{children}</View>;
  },
};

function NativeHTMLRenderer(props) {
  return htmr(props.html, { transform });
}

preserveAttributes

By default htmr will convert HTML attributes to camelCase because that's what React uses. You can override this behavior by passing preserveAttributes options. Specify array of string / regular expression to test which attributes you want to preserve.

For example you want to make sure ng-if, v-if and v-for to be rendered as is

htmr(html, { preserveAttributes: ['ng-if', new RegExp('v-')] });

dangerouslySetChildren

By default htmr will only render children of style tag inside dangerouslySetInnerHTML due to security reason. You can override this behavior by passing array of HTML tags if you want the children of the tag to be rendered dangerously.

htmr(html, { dangerouslySetChildren: ['code', 'style'] });

Note that if you still want style tag to be rendered using dangerouslySetInnerHTML, you still need to include it in the array.

Multiple children

You can also convert HTML string which contains multiple elements. This returns an array, so make sure to wrap the output inside other component such as div, or use React 16.

import React from 'react';
import htmr from 'htmr';

const html = `
  <h1>This string</h1>
  <p>Contains multiple html tags</p>
  <p>as sibling</p>
`;

function ComponentWithSibling() {
  // if using react 16, simply use the return value because
  // v16 can render array
  return htmr(html);
  // if using react 15 and below, wrap in another component
  return <div>{htmr(html)}</div>;
}

Use Cases

This library was initially built to provides easy component mapping between HTML string and React component. It's mainly used to render custom component from HTML string returned from an API. This library prioritize file size and simple API over full HTML conversion coverage and other features like JSX parsing or flexible node traversal.

That's why I've decided to not implement some features (see Trade Off section below). If you feel like you need more features that's not possible using this library, you can check out some related projects below.

Trade Off

  • Inline event attributes (onclick="" etc) are not supported due to unnecessary complexity
  • htmr use native browser HTML parser when run in browser instead of using custom parser. Due to how browser HTML parser works, you can get weird result if you supply "invalid" html, for example div inside p element like <p><div>text</div></p>
  • Script tag is not rendered using dangerouslySetInnerHTML by default due to security. You can opt in by using dangerouslySetChildren
  • Style tag renders it children using dangerouslySetInnerHTML by default. You can also reverse this behavior using same method.

Related projects

HTML to react element:

HTML (page) to react component (file/string):

License

MIT

htmr's People

Contributors

arcath avatar dependabot[bot] avatar dmfrancisco avatar drgx avatar greenkeeper[bot] avatar greggb avatar grubersjoe avatar iamyuu avatar pveyes avatar semmatabei avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

htmr's Issues

HTML attribute `allowfullscreen` will default to `false` when nolength

Had an issue reported by an administrator at work, they noticed that the fullscreen option on our post system was not working. After spending some time debugging, I traced the issue to a typical default behaviour of the internal React prop allowFullScreen.

htmr('<iframe src="some-source" allowfullsreen mozallowfullscreen />');
produces <iframe src="some-source" mozallowfullscreen />

While htmr('<iframe src="some-source" allowfullsreen="1" mozallowfullscreen />'); will produce the desired <iframe src="some-source" allowfullsreen mozallowfullscreen />

This is because the React props generated for the iframe node have allowFullScreen equal to "", which becomes a false-like value to React.

We should I do like this?

if I have a VideoPlayer Component , I want to make a string like '<p><video src="></video></p>' to '<p><VideoPlayer src="" /></p>', We should I do?

Question: Is there any way to ignore some tags?

Hello @pveyes

I'm trying to avoid to transform some HTML tags, something like:

const transform = {
	iframe: null,
    image: null
}

Since at the end it's running: React.createElement(). Is there any way to not transform into a component but still remain the children HTML?

I might do iframe: div and would do the trick, but isn't ideal or even transform to React.Fragment.

Thanks!

Decode HTML attributes on server

Failing testcase:

convert('<a href="https://www.google.com/?a=b&ampc=d">test</a>');

Should returns

<a href="https://www.google.com/?a=b&c=d">test</a>

It works in browser, but not in server

Using encoded HTML entities does not render correctly.

I am trying render some HTML content using your converter.

The content will look something like this:
'<p>Hej &amp;<br />↵&amp;<br />↵<br />↵&amp; &amp; &amp;</p>↵'

When I use your converter it does not render the ampersands correctly. Instead it renders the encoded HTML entities as raw text.

I would bet that the problem occurs here:

const tempEl = document.createElement('div');
function escape(str) {
  tempEl.textContent = str;
  return tempEl.innerHTML;
}

Not sure if this will work but I'd suggest trying out using something like this instead:

function decodeHtml(html) {
  const txt = document.createElement('textarea');
  txt.innerHTML = html;
  return txt.value;
}

Broken server side build using webpack module

Seems like ES modules support from #20 break SSR (webpack with target: 'node'), this is because webpack uses module entry point from package.json but it's should only be used for browser bundle (not server side)

Version 10 of node.js has been released

Version 10 of Node.js (code name Dubnium) has been released! 🎊

To see what happens to your code in Node.js 10, Greenkeeper has created a branch with the following changes:

  • Added the new Node.js version to your .travis.yml

If you’re interested in upgrading this repo to Node.js 10, you can open a PR with these changes. Please note that this issue is just intended as a friendly reminder and the PR as a possible starting point for getting your code running on Node.js 10.

More information on this issue

Greenkeeper has checked the engines key in any package.json file, the .nvmrc file, and the .travis.yml file, if present.

  • engines was only updated if it defined a single version, not a range.
  • .nvmrc was updated to Node.js 10
  • .travis.yml was only changed if there was a root-level node_js that didn’t already include Node.js 10, such as node or lts/*. In this case, the new version was appended to the list. We didn’t touch job or matrix configurations because these tend to be quite specific and complex, and it’s difficult to infer what the intentions were.

For many simpler .travis.yml configurations, this PR should suffice as-is, but depending on what you’re doing it may require additional work or may not be applicable at all. We’re also aware that you may have good reasons to not update to Node.js 10, which is why this was sent as an issue and not a pull request. Feel free to delete it without comment, I’m a humble robot and won’t feel rejected 🤖


FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Microbundle

Have you looked at microbundle at all? Did a quick test and the output is tiny.

microbundle on top:
screen shot 2018-01-23 at 10 52 03 pm

Does not work with server-side rendering

The below error is being thrown:

import parse from 'posthtml-parser';
       ^^^^^
SyntaxError: Unexpected identifier

I was digging around a little bit and... why the server.js file is not being published in CommonJS format? I guess that this might be the problem here.

An in-range update of react is breaking the build 🚨

There have been updates to the react monorepo:

    • The devDependency react was updated from 16.6.1 to 16.6.2.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

This monorepo update includes releases of one or more dependencies which all belong to the react group definition.

react is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of rollup is breaking the build 🚨

The devDependency rollup was updated from 1.9.2 to 1.9.3.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

rollup is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

Release Notes for v1.9.3

2019-04-10

Bug Fixes

  • Simplify return expressions that are evaluated before the surrounding function is bound (#2803)

Pull Requests

  • #2803: Handle out-of-order binding of identifiers to improve tree-shaking (@lukastaegert)
Commits

The new version differs by 3 commits.

  • 516a06d 1.9.3
  • a5526ea Update changelog
  • c3d73ff Handle out-of-order binding of identifiers to improve tree-shaking (#2803)

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Empty String Converted to `true`, Causing React Warning

Test Case

<p class="">Test</p>

Error

react-dom.development.js?f8c1:67 Warning: Received `true` for a non-boolean attribute `className`.

If you want to write it to the DOM, pass a string instead: className="true" or className={value.toString()}.

Expected Outcome

Just pass the empty string through (or omit className entirely).

An in-range update of rollup is breaking the build 🚨

The devDependency rollup was updated from 1.14.2 to 1.14.3.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

rollup is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

Release Notes for v1.14.3

2019-06-06

Bug Fixes

  • Generate correct external imports when importing from a directory that would be above the root of the current working directory (#2902)

Pull Requests

Commits

The new version differs by 4 commits.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Text node preprocessor

Currently we have no way of doing string transformation before render (for example to remove all whitespace after #22), unlike DOM node which can be mapped over using 2nd argument. Maybe we can also use it as mapping. Still not sure about the syntax, possibly empty string?

convert(html, { '': str => str.trim() }

Typescript Typings

I notice that htmr is written in typescript but isn't supplying any types for us to use with it in our code.

Is it possible to export the types during the rollup?

I've always used tsc to compile my npm modules so they include the typings.

Removal of whitespace-only text nodes

Thank you for creating and maintaining this useful library 🙌

I noticed that whitespace was not being preserved between tags. After reading the code I realize that this is on purpose and done by this code (in the browser version):

else if (node.nodeType === NodeTypes.TEXT) {
  return node.textContent.trim() === '' ? null : unescape(node.textContent);
}

Personally I found this unexpected. For example:

convert("<span>hello</span> <span>world!</span>")

Renders to:

helloworld!

But I was expecting:

hello world!

Another example I noticed on a code block that had been syntax highlighted:

convert("<pre><span>Hello</span>\n<span>World</span></pre>")

Renders to:

HelloWorld

But I was expecting:

Hello
World

For my application I created a fork where I'm simply not trimming the whitespace. Let me know if you agree with the expectations and think this behavior should change. Thanks again 👍

Action required: Greenkeeper could not be activated 🚨

🚨 You need to enable Continuous Integration on all branches of this repository. 🚨

To enable Greenkeeper, you need to make sure that a commit status is reported on all branches. This is required by Greenkeeper because it uses your CI build statuses to figure out when to notify you about breaking changes.

Since we didn’t receive a CI status on the greenkeeper/initial branch, it’s possible that you don’t have CI set up yet. We recommend using Travis CI, but Greenkeeper will work with every other CI service as well.

If you have already set up a CI for this repository, you might need to check how it’s configured. Make sure it is set to run on all new branches. If you don’t want it to run on absolutely every branch, you can whitelist branches starting with greenkeeper/.

Once you have installed and configured CI on this repository correctly, you’ll need to re-trigger Greenkeeper’s initial pull request. To do this, please delete the greenkeeper/initial branch in this repository, and then remove and re-add this repository to the Greenkeeper App’s white list on Github. You'll find this list on your repo or organization’s settings page, under Installed GitHub Apps.

Better assertion when comparing server and browser module

Currently we use expect(server).toEqual(browser) to compare render result, which can confuse some people debugging tests.

The better solution would be using custom matcher by extending expect. Still not sure about the method name for assertion, probably toRenderConsistently because we're comparing server render result with browser render result

expect.extend({
  toRenderConsistently() {
    // do something with better error message
  }
});

test('something', () => {
  const html = '<span></span>';
  expect(html).toRenderConsistently();
});

Error if variable is null

I got this error when call htmr with null variable, it's because this variable's value haven't set in redux store, I suggest maybe htmr could catch null variable & return empty string instead.

htmr.min.js:1 Uncaught TypeError: Cannot read property 'trim' of null
    at module.exports (htmr.min.js:1)
    at TopUpUploadPaymentProofModalContainer.render (TopUpUploadPaymentProofModalContainer.js:33)
    at finishClassComponent (react-dom.development.js:10249)
    at updateClassComponent (react-dom.development.js:10226)
    at beginWork (react-dom.development.js:10605)
    at performUnitOfWork (react-dom.development.js:12573)
    at workLoop (react-dom.development.js:12682)
    at HTMLUnknownElement.callCallback (react-dom.development.js:1299)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:1338)
    at invokeGuardedCallback (react-dom.development.js:1195)

Some HTML Attributes are not being mapped to camelCase

Hello,

I recently ran into an issue with my project where some HTML attributes are not contained in the src/attribute.json file and are not converted into camelCase as a result of the exclusion. I do not see a security concern that would cause these properties to be excluded and would like to know if the exclusions were intentional, and if anyone is willing to share the reasoning behind the exclusions before I add them in my code. The HTML properties in question are:

  1. itemprop (itemProp) - https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/itemprop
  2. itemscope (itemScope) - https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/itemscope
  3. itemtype (itemType) - https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/itemtype

I included links to show that all 3 of these properties are valid HTML global attributes. Please let me know if I am overlooking something, or if there is an underlying reason these properties have been excluded from mapping in this project.

Thank you for your time and support on this project :) I would be willing to submit a PR with the new attributes if you agree they should be present in this project.

Have a nice afternoon.

Uncaught TypeError if given invalid html string. Better error message?

How to repro:

// invalid html string
const input = '<a href="google.com></a>'

const htmr = require('htmr')(input)
TypeError: Cannot read property 'tag' of undefined
    at transform (/home/raibima.putra/node_modules/htmr/lib/index.js:105:18)
    at /home/raibima.putra/node_modules/htmr/lib/index.js:132:12
    at Array.map (<anonymous>)
    at convertServer (/home/raibima.putra/node_modules/htmr/lib/index.js:131:24)
    at repl:1:1
    at ContextifyScript.Script.runInThisContext (vm.js:50:33)
    at REPLServer.defaultEval (repl.js:240:29)
    at bound (domain.js:301:14)
    at REPLServer.runBound [as eval] (domain.js:314:12)
    at REPLServer.onLine (repl.js:441:10)

I spent hours trying to figure out what went wrong. It would have been easier if the error message was a little bit more descriptive.

FYI:

// invalid html string
const input = '<a href="google.com></a>'

const parse = require('posthtml-parser')
parse(input)

will output

[ undefined ]

htmr Not Rendering Tags in Head When Element Key is "0"

I have js and CSS code fetched from CMS, but when I want to render the code into the document head it does not work

import React, { ReactNode, createContext, useEffect } from "react";

import Head from "next/head";
import { useRouter } from "next/router";

import htmr from "htmr";

import { useFetchTenantQuery } from "@/store/slices/api/tenantSlice";

interface ProviderProps {
  children: ReactNode;
}

const renderHtml = (htmlCode: string | null | undefined) =>
  htmlCode && htmr(htmlCode, { dangerouslySetChildren: ["script", "style"] });

const InjectScriptsContext = createContext({});
const InjectScriptsProvider: React.FC<ProviderProps> = ({ children }) => {
  const { data: tenant } = useFetchTenantQuery();

  return (
    <InjectScriptsContext.Provider value={{}}>
      <Head>{renderHtml(tenant?.meta.custom_head_code)}</Head>
      {children}
    </InjectScriptsContext.Provider>
  );
};

export { InjectScriptsProvider, InjectScriptsContext };

not working code example:

<style>
    :root {
    --white: #FFF;
    --gray: #363638;
    --gray-mid: #9D9D9D;
    --gray-dark: #3e3e3e;
    --gray-hover: #F7F7F7;
    --body-background: #1F192D;
    --primary: #8543CC;
    --primary-border: #42375d;
    --text: #CEC0E0;
    --widget-background: #3A2D54;
}
</style>

it work only when I add anything before the style tag, like comments:

<!--Start of CSS code-->
<style>
    :root {
    --white: #FFF;
    --gray: #363638;
    --gray-mid: #9D9D9D;
    --gray-dark: #3e3e3e;
    --gray-hover: #F7F7F7;
    --body-background: #1F192D;
    --primary: #8543CC;
    --primary-border: #42375d;
    --text: #CEC0E0;
    --widget-background: #3A2D54;
}
</style>
<!--End of CSS code-->

the issue seemed to accrue only when I render the code in the head I tried to render the code into body it worked fine,
also, I was trying to add scripts to the code like:

<script type="text/javascript">
    console.log("testing head")
</script>
<style>
    :root {
    --white: #FFF;
    --gray: #363638;
    --gray-mid: #9D9D9D;
    --gray-dark: #3e3e3e;
    --gray-hover: #F7F7F7;
    --body-background: #1F192D;
    --primary: #8543CC;
    --primary-border: #42375d;
    --text: #CEC0E0;
    --widget-background: #3A2D54;
}
</style>

the script tag was not rendered, I tried to add a comment before the script tag and everything worked as expected.

I was trying to troubleshoot the issue I found out that the issue happens on the first element only when the key is "0" I tried to create similar elements and set a unique key it worked without any issue:

const someElements = [
    React.createElement("style", {
      //any key other than 0 will work
      key: "1",
      children: null,
      dangerouslySetInnerHTML: {
        __html:
          ":root {\n    --white: #FFF;\n    --gray: #363638;\n    --gray-mid: #9D9D9D;\n    --gray-dark: #3e3e3e;\n    --gray-hover: #F7F7F7;\n    --body-background: #1F192D;\n    --primary: #8543CC;\n    --primary-border: #42375d;\n    --text: #CEC0E0;\n    --widget-background: #3A2D54;\n}"
      }
    }),
    React.createElement("script", {
      type: "text/javascript",
      key: "2",
      children: null,
      dangerouslySetInnerHTML: {
        __html: 'console.log("testing head")'
      }
    })
  ];

Problems with empty attributes

// Convert attribute value to boolean attribute if needed

Hi the way this detects truthy attributes does make sense, but since the definition for the RawProperties doesn't allow for boolean values, it gets type casted to a string with the value "true"

export type RawAttributes = {

This is causing problems parsing and rendering some things. For example
<img alt="" src="Something"/> will end up with alt="true" and if you substribute a react component on top it will complain that alt="true' ie. Warning: Received truefor a non-boolean attributealt``
Thanks
Andy

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.