Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Render to String #50

Closed
jridgewell opened this issue Jul 12, 2015 · 15 comments
Closed

Render to String #50

jridgewell opened this issue Jul 12, 2015 · 15 comments

Comments

@jridgewell
Copy link
Contributor

One of the perks of React is the ability to render its virtual dom into strings on the server. It'd be nice to have the same capabilities.

Do you think it'd be worth while to maintain string element* that could be required on the server?

// string.js

var elements = require('virtual_elements'),
    elementVoid = elements.elementVoid,
    elementOpenStart = elements.elementOpenStart,
    elementOpenEnd = elements.elementOpenEnd,
    attr = elements.attr;


var patch = function(node, fn) {
  // Will have to consider how to translate this.
  // For now, just run the patch function
  fn();
};

var elementOpen = function(tag, key, statics, ...attrs) {
  // combine statics and attrs
  // translate combined into strings `${name}="${value}`
  return `<${tag} ${combined}>`;
};

// Void tags aren't gonna like this, but psuedocode. :smile:
var elementClose = function(tag) {
  return `</${tag}>`;
}

var text = function(value) {
  return value;
};


module.exports = {
  patch: patch,
  elementVoid: elements.elementVoid,
  elementOpenStart: elements.elementOpenStart,
  elementOpenEnd: elements.elementOpenEnd,
  elementOpen: elementOpen,
  elementClose: elementClose,
  text: text,
  attr: elements.attr
};
@cramforce
Copy link
Contributor

I think this would be a nice feature! It is not high on our priority list since we want to use this with closure templates which has existing string based backends.

@paolocaminiti
Copy link
Contributor

I've uploaded a quick proof of concept doing string rendering here: https://github.com/paolocaminiti/incremental-dom-to-string

calling .patch can render a string of the whole description function output or write directly to a stream.

@sparhami
Copy link
Contributor

Definitely seems like an external library is the way to go for this, especially since you might want to tie it in with out you flush down changes. Simply rendering to a string could have some uses, but ultimately there are a lot of different trade-offs to be made.

@eduardolundgren
Copy link
Contributor

Server side rendering can be achieved trivially by using Soy with incremental dom, although the growth of popularity of other JavaScript-only-templates alerts the need of this feature, as suggested previously by @jridgewell.

Being able to render incremental dom as string without external dependencies like jsdom is becoming more important to the library ecosystem. A recent jsdom benchmark shows it's probably not good for a real use case of server side rendering.

Is there any library that already provides it? If not, this issue should be reopened for discussion.

Thank you.

@ewinslow
Copy link

I agree that idom should just stay focused on patching DOM, but maybe it would help to have a list of what DOM APIs idom depends on. Then a minimal in-memory DOM API can be created which is compatible with IDOM. Instead of passing a full HTMLElement to IDOM to patch, we could pass this minimal in-memory version. That could be used either for rendering on the server, or rendering from a web worker, or what have you.

@eduardolundgren
Copy link
Contributor

@ewinslow that would be ideal, as reference, this library does html to incdom. The opposite direction, incdom to html, may be even smaller.

@jridgewell
Copy link
Contributor Author

@sparhami's point is that DOM API is completely unnecessary for this. Instead, you'd use a library that implements iDOM's API (patch, elementOpen, elementClose, elementVoid, etc). As you call the functions, the string (kept in the patch context) gets appended. When patch returns, it returns the complete string.

@eduardolundgren
Copy link
Contributor

@jridgewell yep, using a DOM API is unnecessary, patching iDOM's api is simpler.

Just found this prototype, seems like it's not being maintained though. Did you happen to have something that solved your initial proposal? If not this could be an interesting library to exist. Just want to make sure if we do that we don't spent time in something someone else already have.

@jridgewell
Copy link
Contributor Author

Nothing that I know about.

@ewinslow
Copy link

Easier said than done :)

elementOpen returns a node, so exposes the fact that it is working with DOM under the hood. Either way, you need some kind of fake DOM implementation if you're going to support all iDOM clients...

@sparhami
Copy link
Contributor

elementOpen returns a node, so exposes the fact that it is working with DOM under the hood. Either way, you need some kind of fake DOM implementation if you're going to support all iDOM clients...

I suppose if you want to inject HTML or something, having access to the Element on server side could be useful. I think abstracting that out one level might be a good idea. So instead of:

const el = elementOpen(...)
  el.innerHTML = 'foo'; // of course you would want to be a bit smarter here
elementClose(...);

You might want to do something like:

elementOpen(...);
  setInnerHTML('foo');
elementClose(...);

And then setInnerHTML can use currentElement() on client side and just append to the buffer on server side.

Not sure about other uses of grabbing the element, but it seems like you might want to avoid doing too much with it if you want to render on the client side.

@eduardolundgren
Copy link
Contributor

We've put together an initial implementation of incremental-dom-string.

import {
  elementOpen,
  elementClose,
  text,
  renderToString
 } from 'incremental-dom-string';

const output = renderToString(() => {
  elementOpen('div');
    text('Hello world');
  elementClose('div');
});

where output would correspond to

<div>
  Hello world
</div>

Implementation details feedback would be appreciated, specially at:

  1. Attribute hooks assumes el as a buffer instead Element.
  2. The currentPointer represents the node that will be evaluated for the next instruction, string renderer doesn't know this information and returns placeholder.

We are testing in production, it's working well for the cases we've identified.

@eduardolundgren
Copy link
Contributor

@jridgewell when you have a chance let us know if the implementation posted earlier is aligned with what you've proposed in this issue, thx

@eduardolundgren
Copy link
Contributor

We are using the referenced implementation in production for different products, would be ideal to have feedback from the community.

@sparhami, since we didn't hear anything from mr. busy man @jridgewell :) would you mind giving your feedback on the implementation referenced?

@sparhami
Copy link
Contributor

One thing I would caution about is sanitizing attribute values. For example, if you do something like:

elementOpen('div', null, null, 'title', user.name);
elementClose('div');

If the user.name is something like "><script>alert('hello')</script>, with the client-side Incremental DOM this is fine because it directly sets it an attribute value (using setAttribute). The server rendered string will be:

<div title=""><script>alert('hello')</script>"></div>, which will run the content of the script. If you are using content security policy for script src, I think you will be fine, but not everyone might be. You might also want to prevent user data from being able to inject other arbitrary html.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants