React: Allow html conditional comments and doctype

Created on 6 Feb 2014  路  56Comments  路  Source: facebook/react

I'm hacking on an fully server and client compatible app using React. One obstactle I'm running into is it seems react doesn't like exclamation marks in your jsx-style code.

Here's an example of a pretty standard boilerplate for HTML:

<!doctype html>
  <!--[if IE 7]>    <html class="no-js ie ie7 ltie8 ltie9"><![endif]-->
  <!--[if IE 8]>    <html class="no-js ie ie8 ltie9"><![endif]-->
  <!--[if gt IE 8]> <html class="no-js ie gtie8"><![endif]-->
  <!--[if !IE]><!--><html class="no-js" xmlns:og="http://opengraphprotocol.org/schema/" xmlns:fb="http://www.facebook.com/2008/fbml" ><!--<![endif]-->
    <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <title></title>
      <meta name="description" content="">
      <meta name="viewport" content="width=device-width, initial-scale=1">

      <link rel="stylesheet" href="/css/app.css" type="text/css" media="all" />
    </head>
    <body>
      <!--[if lt IE 8]>
        <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
      <![endif]-->

      {this.props.page}

      <!-- <script src="/js/vendor.js"></script> -->
      <script src="/js/app.js"></script>
    </body>
  </html>

I think it's getting stuck on the conditional comments in this example.

I would prefer to keep this code in the HTML style, and to render it from the server without making major changes, it needs to be inside a react class. I'd be open to trying to hack this together, I may take a stab sometime this coming week.

Most helpful comment

While this is true it sucks for people who want to do full-page server rendering. I think if we claim to support full-page server rendering we should at least support doctype.

All 56 comments

After checking further, it did seem to break on the doctype (!) as well.

JSX is not HTML. You'll want to look at the HTML to JSX compiler. http://facebook.github.io/react/html-jsx.html

You won't be able to generate doctypes or conditional comments with React. This is technically impossible by design. If you want functionality like this, you might consider creating a script or addon to html-jsx to generate a javascript (JSX) equivalent.

While this is true it sucks for people who want to do full-page server rendering. I think if we claim to support full-page server rendering we should at least support doctype.

I can see a good reason for leaving out these portions of the HTML spec. I ended up adding on to this branch of react-app-middleware some changes to allow pulling in a static HTML template initially and then rendering components results into it.

See an example of it being used in this repo (see server.js).

@petehunt I agree with you but I also think it's not really that practical to render full pages inside of react.

I think a good reason against is that React is used by many as a client side library, and therefore should be kept as light as possible.

Perhaps there could be an extension made that implements the "rest" of the HTML5 spec, but could be mixed in only on the server side as client's shouldn't need to render stuff like doctype once the server has.

I guess you could use React.DOM.injection and modify the JSX transform to read Plain conditional comments could probably be done similarly.

@natew
How do you deal with void tags like <img> and <meta> right now? Do you self-close them before embedding them?
Would you use a tool like html-jsx to transform the html to syntax that the jsx transformer understands?

I have images working like this: <img src={this.imagePath + image.thumb} /> with no problems.

I have played with the html to jsx transformer thats on React's site currently, but haven't needed it yet personally. Is there a document that outlines all the supported tags? I haven't checked, but I also haven't gotten too deep into things yet.

Edit: As for the self-closing, yea I've always felt more accustomed to using them anyway.

On second thought, I think I may have missed the point above. Adding something like React.DOM._doctype would be useful, and would have very little impact on React's code.
Same could probably be done with conditional comments (React.DOM._ConditionalCommentHide ,React.DOM._ConditionalCommentShow). Inserting conditional comments using innerHTML might work? I'm not sure how updating them will work though.

I don't think there's a document for it (@Daniel15 )
A few issues I've seen with the html-jsx transformer:
strips script tags
strips html head body tags
<style> tag content isn't transformed
namespaced attributes (xmlns:fb or data:myattr) don't work
There's a few issues with css styles and attribute case-sensitivity in another github issue.
Since it uses the browser dom (you can use jsdom so you don't have to use a browser), all tags supported by browsers should work. Has worked pretty well for me.

html-jsx transformer does things like turn class -> className and style strings into objects and turn void tags (<img>) into self-closing tags (<img/>).
I'm creating tools that transform user-provided html/data (as-is) into components, so I've had to make use it.

I too would like to see JSX support HTML conditional comments and not do the things @jhiswin described. I was going to implement a JSX preprendering tool until I found out I couldn't even write HTML comments and doctype...

I'm not sure if html comments are a deal breaking use case -- are conditional comments the only solution to these problems? -- and doctype can be prepended by your web server request handler or static build system.

Prepending doctype is easy, conditional comments are harder to because that's the easiest way to conditionally load IE shims, short of this option I'll have to write a bunch of JS pre-shims for browser detection or fiddle with the webserver config, which I may not always have access to. IE8 users are still ~25% of the market. Is it difficult to elevate HTML comments to have same status as JS comments in your Esprima fork?

@jeffmo

@wyuenho As another take on it, you could have a preprocessor (like html-jsx-lib) turn

My view on it is that you will need a preprocessor to deal with differences between HTML and JSX anyways.

@jhiswin Or better yet, preprocess to JSX. As a hack it'll work, just have to make sure to spit out the right string what renderComponentToString is called. The extra setup is a hassle tho. This also denies the claim that React is designed to be easily rendered on the server.

I still think it's best to have JSX support as many HTML5 constructs as possible. Just fewer surprises.

BTW: rendering on the server doesn't necessarily mean that we need to be able to render full pages, especially since browser support for updating <html> and <head> is so sketchy. We've gone back and forth as to whether we want to actually support this or not for this reason.

We could probably get comments into core with a little work (mostly just cloning ReactTextComponent to work with comments and figuring out how to look it up by ID), not sure about the jsx transform. Not sure if we can prioritize this relative to other things (especially since this can be worked around), but I think a PR would definitely be considered.

Will look into it this weekend. What's the release schedule for 0.9?

Sometime between Tuesday and Friday

No rush then :)

Comments will be interesting because the typical setup is to have a number of them all in a row, without a closing tag. Not sure the syntax or tricks you'd have to do to build a root level component that has all the opening tags in conditionals.

Just a heads-up, there are a bunch of bugs with comments in IE8 in particular I think, where they get merged or get lost IIRC. Shouldn't be a problem for server-rendering, but it may mess up client-side _mountIndex etc.

Has there been any further progress on bringing doctypes into React?

@balanceiskey React can't mount higher than body client-side, so I doubt there is much pressure to get it done. And I'm not sure if I understand the issue, can't you just prepend a fixed doctype before the React output?

@syranide Prepending doctype just feels a touch hacky. That said, you're right, it's probably not a huge issue.

I think React's rendering model needs to be separate from the DOM. There are React components for the html, body and iframe tags, so the idea of the DOM being separate is a good one, IMHO.

Prime use case is iFrames as a component, which has a DOM itself and therefore INCLUDES doctype, HTML, HEAD, BODY and other top-level tags, so I think if iframe is actually officially being supported as a component then it's fundamental to support these other components.

Should I not be using React to render complete documents before serving?

@adjavaherian You shouldn't need to.

Actually I think React CAN mount higher than body client-side: In the caes where you want react to render an iFrame... What's the current "best of breed" for iFrame generation, where you want react to control the iFrame's content? This seems largely impossible at the moment.

Also, does dangerouslySetInnerHTML cause the browser to (re-)run JS that is in that string?

Also, does React do its diffing on that dangerous setting? Doing a full replace would cause the entire document of the iFrame to stutter on update, and all the resources in it to reload on most browsers if it's a set rather than a diff/update, I think?

Not sure how the diffing would work with sanboxed JS running in the iFrame though?

Also, does React have a grouping component that doesn't actually do anything but mount other components? (kind of a silent component). I couldn't seem to see one. Probably should learn more about the internal architecture, I guess.

Also, does React have a grouping component that doesn't actually do anything but mount other components? (kind of a silent component). I couldn't seem to see one. Probably should learn more about the internal architecture, I guess.

I don't know much about it but I saw these links:

https://developer.zendesk.com/blog/rendering-to-iframes-in-react
http://jsfiddle.net/YXgzf/

Thanks, @gaearon :) I found NodeList and glossary subsequent to my last comment. And yeah, I've read the zendesk article... I've also implemented his stuff to a degree, but it has large holes in it when it comes to stuff above body (like styles, script, etc.), and the whole approach feels like a big hack-around... (fighting the code) hence this thread :)

Is there any movement on this, @wyuenho @petehunt ?

We render full react pages, using react-router, on the server.

An easy workaround was just adding the doctype to the returned markup on the server:
res.send('<!DOCTYPE html>' + markup);

Initially I thought it might cause an invariant on the client, but it doesn't seem to cause any problems.
To be sure, you could prepend the same doctype string in your client-side entry/rehydrate file.

I've found a hack way to insert comments to html:
https://nemisj.com/conditional-ie-comments-in-react-js/

+1 doctype for full-page server rendering.

For my usecase, I use React to render emails using React.renderToStaticMarkup().
It would be nice to add conditionnal comments for Outlook.

In React 0.14, rendering the <body> tag (at least with the test utils) raises an invariant violation. Not sure if I should be mentioning that here, or make a new issue. I figured it applies to this.

@zackify That's unrelated, please file a new issue with steps to repro

Any update on this?

No update.

I've just ran into this. The ability to render full page with just react can help to deal with SEO, title and many other things with much flexibility. And you won't even need something like helmet or react-document-title. And it simply seems logical to me to handle whole application state within react. While prepending doctype is easy, other things are really hard or impossible sigh.

Well, I found out that some people say that changing head with virtual DOM can push some browsers to go crazy, which is definitely worse than not having whole app within react. Maybe, it wasn't a good idea after all...

@baygeldin Specifically, can you define "go crazy"? Which browsers, changing which nodes?

@jimfb I guess I should have said "an unexpected behavior". I couldn't find a good example, but from what I understood the main arguments are: 1) Browsers do not expect replacing the whole nodes in the head, so their behavior is not standardized 2) It's a common practice for browser-extensions to inject external resources to the head, react will override those if it handles the head tag. Also, according to MDN, document.head is read-only. Sorry, I don't have concrete examples and time to investigate it :( I decided to go the standard way of doing things.

+1 for this. It's quite a bit unexpected that server-side react can't actually render the whole page.

Hi!

I have found a new method to include HTML comments (e.g. conditional IE comments) in JSX and React components using a Web Component.

https://github.com/optimalisatie/react-jsx-html-comments

<react-comment>[if lt IE 8]</react-comment>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<react-comment>[endif]</react-comment>

Hello, I added additional component Head in my server-side to resolve this situation:

class Head extends React.Component{
    render() {
        let dangerousInnerHTML: string[] = this.props.children.map(value => {
            if (typeof value !== 'string')
                return ReactDOMServer.renderToStaticMarkup(value);
            else
                return value;
        });

        return (
            <head dangerouslySetInnerHTML={{ __html: dangerousInnerHTML.join('') }}>
            </head>
        );
    }
}

and now I can just use it:

        return (
            <html lang="ru">
                <Head>
                    <meta charset="utf-8" />
                    <title>Example</title>
                    <meta name="viewport" content="width=device-width, initial-scale=1" />
                    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
                    <link rel="stylesheet" href="stylesheets/views/index/index.css" />
                    <script src="javascript/index/bundle.js"></script>
                    {`
                        <!--[if lte IE 8]><link rel="stylesheet" href="javascript/css/ie8.css" /><![endif]-->
                        <!--[if lte IE 9]><link rel="stylesheet" href="javascript/css/ie9.css" /><![endif]-->
                    `}
                 </Head>
...

Maybe this is not perfect solution and requires some additional code. Please, review and say what do you think about it. Thanks.

@sumbad The children can contain an array, this requires a separate check, because ReactDOMServer.renderToStaticMarkup can not render arrays.

This is definitely something needed for server side rendering. For right now we have to inject it after we call renderToStaticMarkup. An official way would be much better.

+1

Closing as there is an easy workaround (string manipulation before sending the markup for doctype, and dangerouslySetInnerHTML for comments). React also doesn鈥檛 support IE8 anymore, and the only remaining browser for which conditional comments are relevant is IE9.

Adding support for comments or doctype would have to span multiple projects (React, Babel, ESLint, etc) and yields little benefit, so it is unlikely we鈥檒l be working on this.

For everyone still finding this issue and wondering just how to use dangerouslySetInnerHTML in your code, have a look at this article: https://nemisj.com/conditional-ie-comments-in-react-js/

TL;DR:

renderHead() {
  return (
    var comment = '<!--[if lte IE 9]><script src="/public/media.match.js"></script><![endif]-->';
    <head>
      <title>Website title</title>
      <meta name="react-comment-hack" 
          dangerouslySetInnerHTML={{__html: comment}}>
      </meta>
    </head>
  );
}

still error

Invariant Violation: meta is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`

i'm useing this to fix it
template = '<!DOCTYPE html>' + ReactDOMServer.renderToString(<Template />)

@sumbad stumbled across this and years later it looks like you're on the right path.

Your solution didn't work with Context, so here's an updated solution that does that and adds some generic helpers in the same approach as styled-components.

import React, { useContext } from "react";
import ReactDOMServer from "react-dom/server";

export function RawHTMLWrapper({
  as,
  children = [],
  contexts = [],
  renderer = ReactDOMServer.renderToStaticMarkup,
  createElement = React.createElement,
  ...rest
}) {
  const values = contexts.map((c) => useContext(c));

  const toHtml = (value) => {
    if (typeof value !== "string") {
      const element = contexts.reduce((el, c, idx) => {
        return <c.Provider value={values[idx]}>{el}</c.Provider>;
      }, value);
      return renderer(element);
    } else {
      return value;
    }
  };

  let dangerousInnerHTML;
  if (Array.isArray(children)) {
    dangerousInnerHTML = children.map(toHtml).join("");
  } else {
    dangerousInnerHTML = toHtml(children);
  }

  const props = {
    ...rest,
    dangerouslySetInnerHTML: { __html: dangerousInnerHTML }
  };
  return createElement(as, props);
}

const elements = [
  "div",
  "tr",
  "table",
  "tbody",
  "td",
  "h1",
  "h2",
  "h3",
  "h4",
  "h5",
  "h6",
  "head",
  "body",
  "html",
  "img",
  "span",
  "a",
  "button",
  "ul",
  "ol",
  "li"
];

const reducer = (acc, el) => {
  const Componet = ({ children, contexts, ...rest }) => (
    <RawHTMLWrapper as={el} contexts={contexts} {...rest}>
      {children}
    </RawHTMLWrapper>
  );
  return { ...acc, [el]: Componet };
};

export const raw = elements.reduce(reducer, {});

export function Raw(contexts) {
  const reducer = (acc, el) => {
    const Componet = ({ children, ...rest }) => (
      <RawHTMLWrapper as={el} contexts={contexts} {...rest}>
        {children}
      </RawHTMLWrapper>
    );
    return { ...acc, [el]: Componet };
  };
  return elements.reduce(reducer, {});
}

And then using it via raw.div etc.

    <FooContext.Provider value="nested context">
      <h1>raw-components</h1>
      <div>
        Useful for rendering custom HTML content (e.g. comments). Inspired by
        styled components.
      </div>
      <raw.ul contexts={[FooContext]}>
        <li>Escapes nested {`<html>`}</li>
        <raw.li>Doesn't escape in {`<b>raw</b>`} components</raw.li>

        <raw.li>
          {`<!-- Before some comments -->`}
          Supports HTML comments (inspect source here)
          {`<!-- After some comments -->`}
        </raw.li>
        <raw.li
          style={{ color: "red" }}
          title="and supports titles"
          className="boldone"
        >
          Supports style props (etc.)
        </raw.li>
        <li>
          Supports <Consumes />
        </li>
        {`<li>Supports bad HTML (e.g. no closing tags, thanks browsers!)`}
      </raw.ul>
    </FooContext.Provider>

You can see it running here: https://codesandbox.io/s/react-raw-html-wrapper-hc2jq?file=/src/RawHTMLWrapper.js:91-674

Was this page helpful?
0 / 5 - 0 ratings