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

Inquries: i18n interpolation, first-page problem #227

Closed
bitinn opened this issue Apr 8, 2015 · 30 comments
Closed

Inquries: i18n interpolation, first-page problem #227

bitinn opened this issue Apr 8, 2015 · 30 comments

Comments

@bitinn
Copy link
Contributor

bitinn commented Apr 8, 2015

I have been using virtual-dom for my projects in the past 6 months, while it's an absolute joy 99% of the time, these 2 topics bother me quite a bit:

(They bother me mostly because I don't know if I am doing them as elegantly as possible. And both are not direct problems with virtual-dom, but using virtual-dom with other modules.)


Q1 - We use node-polyglot for i18n, and like most i18n libraries they support string interpolation, but in order to mix polyglot output with virtual-dom, we need to do following:

h('p.message', {
    innerHTML: i18n.t('msg.general', {
        message: '<a href="/help" class="link">' + i18n.t('msg.error') + '</a>'
    })
})

// msg.general = 'Hi, %{message}'
  • Obviously we would like to avoid hardcoding any html, so <a href="/help" class="link"> should preferably be generated with h
  • And to keep our vnode p.message flexible, we should avoid innerHTML approach as well

So ideally we need to go vnode -> string -> vnode in order to use i18n interpolation involving html. That involves:

  • Generate message vnode with h and use vdom-to-html to convert into string
  • Pass it as an object attribute into polyglot, which produce the message content, as html string
  • Use html-to-vdom to convert content into vnodes, pass them as children into vnode p.message

I am not sure how expensive this would be, should I just use innerHTML for performance?


Q2 - Existing frameworks like mercury focus on single page app (most examples are client-side rendering); we use virtual-dom extensively server-side for first-page view rendering, and want to add client-side rendering as a progressive enhancement.

I have seen a few discussions (#49, #203) on this, and know that virtual-dom isn't looking to do complex client-side binding (that allows client-side rendering to kicks in when real dom already exists).

Does it mean currently the best approach for mixing server-side and client-side rendering, is to re-render the whole document again when client-side data is ready? I can't think of a better way, and would like to believe virtual-dom is fast enough when re-rendering full document.

How do you approach this problem? mercury encourages no manual dom manipulation, which I assume applies to virtual dom libraries in general.


In short, sorry for posting 2 long questions, but hopefully these 2 questions are common enough to worth answering :)

@Zolmeister
Copy link
Contributor

For Q2 see #203

For Q1, I don't understand, why wouldn't the following suffice?
(in other words, why are you translating an html string and not the contents)

h('p.message', {
  h('a.link', {href: '/help'}, i18n.t('msg.error'))
})

@bitinn
Copy link
Contributor Author

bitinn commented Apr 8, 2015

@Zolmeister Thx for the reply, but I did read #203, see my OP, I am wondering the conclusion it reached: if we avoid patching real dom, then the only solution to me, is to replace it wholesale.

As for Q1, you are missing out the interpolation part, imagine a i18n mapping like

// in english i18n file
msg.welcome = 'Hi, %{user}, today is %{date}.'
...
// in chinese i18n file
msg.welcome = '你好,%{user},今天是%{date}。'

To avoid interpolation you need to split welcome message into 5 strings, which doesn't make it easier to manage. %{user} can be either text string or html, otherwise i18n file will be difficult to maintain.

@Zolmeister
Copy link
Contributor

My understanding is that you generate the first diff via html-to-vdom and simply apply it to the DOM as normal.

As for the i18n interpolation, I guess I think interpolating over the html is a mistake (in general), and the answer really is to break it up into components

@bitinn
Copy link
Contributor Author

bitinn commented Apr 9, 2015

Yeah ideally we wouldn't need interpolation with html, but human languages are not designed with programmers in mind, eg. word order changes, divide english sentences into word phrases (or divide them based on dom nodes), you will soon find yourself unable to translate them into other languages properly.

I don't think there is a way to avoid html string being used in i18n interpolation, unless we explicitly structure sentences with templating language constraints in mind.

For other templating system, these are often done through specifying the input is html safe. For virtual-dom, innerHTML looks like the obvious approach, but sacrifices diff-ing flexibility in the process. To do it correctly, vnode -> string -> i18n -> vnode seems unavoidable.

Is it possible to extend virtual-dom hyperscript with node-polyglot translation feature, through a plugin of sorts? How do one get started on such a plugin?

@bitinn
Copy link
Contributor Author

bitinn commented Apr 9, 2015

For the client-side rendering problem, is html-to-vdom designed to be used to convert full document into vnode, and then diff-ing it through patches? I see its test cases do cover just able everything. cc @TimBeyer

I am not sure if it would be faster than just rendering the full document and replace it in one go? Can traversing real dom and patching them be slower? I see more researching are to come at #222

@Matt-Esch
Copy link
Owner

If you want different vnode structures for different phrases, you're going to have to build something on top of virtual-dom where you can define a vtree in some form that suits you, and create a lookup system for the structures, using i18n under the hood to do translations. If you are putting html in your translation strings you can probably parse the html and convert the structures to template functions using one of the html to vdom libraries out there.

@bitinn
Copy link
Contributor Author

bitinn commented May 5, 2015

I have been dealing with this problem lately, and while approach like using html-to-vdom to obtain a vdom from html do work, it does add quite a bit of dependencies to frontend.

200kb+ minified js to be exact, since it depends on htmlparser2, which is a very good parser but mostly designed for node.js environment.

So for simplicity sake I still prefer wiping out existing server-rendered dom, and using virtual-dom to re-render the whole page again (when js are loaded and data are available, otherwise it behaves like a traditional page-based website).

I believe React is the only implementation of virtual dom that actually attempts to bind client-side virtual dom into existing real dom? All other vdom solutions avoided this to keep implementation simple, and they indeed run much faster.

@alexmingoia
Copy link

Can you use https://github.com/marcelklehr/vdom-virtualize ? It does not require a parser since it uses innerHTML to parse into DOM node first then translates that into VDOM.

@bitinn
Copy link
Contributor Author

bitinn commented May 5, 2015

Thx! I was pointed to https://github.com/tbranyen/diffhtml in another thread as well, use the same technique you mentioned.

But we do need to wipe existing server-rendered dom and replace it with a virtual-dom, right? I don't know how React did it without destroy existing dom, from many examples it appear to:

  1. able to see the real dom from server is the same dom you will get by rendering client-side, thus doesn't re-render.
  2. able to attach events automagically on real dom.
  3. able to extract data from real dom to minimize ajax needed.

That's huge amount of automation (and probably a lot of code and tests).

@alexmingoia
Copy link

If you create an initial vtree from your existing HTML, then the resulting diff of the new tree should not produce any DOM operations if it hasn't change since you rendered from the server. It will not replace everything.

@staltz
Copy link
Contributor

staltz commented May 5, 2015

Just trying to understand your problem...

Does it mean currently the best approach for mixing server-side and client-side rendering, is to re-render the whole document again when client-side data is ready? I can't think of a better way, and would like to believe virtual-dom is fast enough when re-rendering full document.

What is the problem in re-rendering client-side?

@bitinn
Copy link
Contributor Author

bitinn commented May 5, 2015

@alexmingoia I will dig into it further, what you describe sounds like what I expect from React.

@staltz I think the main problem I had was: now we have a complete document from server, we should try to reuse those dom/data for as much as possible, to avoid replacing it again client-side, which can be bad for battery, network, or experience (say user has scrolled, re-render the whole document might cause jump).

@staltz
Copy link
Contributor

staltz commented May 5, 2015

which can be bad for battery, network, or experience (say user has scrolled, re-render the whole document might cause jump).

Unless you have a truly huge page, like some monolithic news paper site, client-side rendering should be normally in the milliseconds (10ms -- 150ms). It is not nearly enough processing to affect battery. User's experience should be unaffected too, since 150ms is too fast to read+interact. Of those, maybe only redundant network calls could be the problem. In fact, if rendering depends on network calls, then that could truly make client-side rendering pass the milliseconds mark. But in that case you should do something smarter, having some kind of flag given to the app, where flag=true on the server-side would make initial network requests, but flag=false on the client-side would not make initial requests.

@Zolmeister
Copy link
Contributor

Zorium-seed does server-side network requests and pre-loads the data client-side: https://github.com/Zorium/zorium-seed

@bitinn
Copy link
Contributor Author

bitinn commented May 5, 2015

@staltz @Zolmeister Thx for the breakdown and suggestions, but I do prefer PE considering other aspect of our site, which is mostly page-based, content-focus, and not single page application.

You might argue virtual-dom is better for SPA, but discussion so far suggest it works well with PE too.


My final question before closing this ticket:

I would really love a simple explanation on how vdom-virtualize and virtual-dom together prevent a re-render of existing html, what I see:

  1. vdom-virtualize can create a vtree (vnodes) from existing document.body, let's call it oldTree
  2. now we get data from backend with ajax, and generate newTree using client-side render function
  3. we then diff(oldTree, newTree) as usual, and obtain patches
  4. now we can just patch(document.body, patches)?

Step 4 is where I am confused, this means as long as we know patches are intended for a specific dom node, we can happily apply it?

@bitinn bitinn closed this as completed May 5, 2015
@neonstalwart
Copy link
Collaborator

@bitinn yes, you can apply patches to any dom tree. if that dom tree matches the rendering of oldTree then the patches will make sense. virtual-dom maintains no state related to the dom nodes used in the previous render.

i'm not familiar with vdom-virtualize but in your 4 steps i'm not seeing where you would apply event handlers. does the first diff more or less just produce patches that only add event handlers?

@bitinn
Copy link
Contributor Author

bitinn commented May 6, 2015

@neonstalwart It's a good question, so without createElement step, we are still not getting the same result we would expect from a full client-side render. @alexmingoia have you had any experience with that?

@bitinn
Copy link
Contributor Author

bitinn commented May 6, 2015

To sum up our discussion so far, virtual-dom + vdom-virtualize (or other html to vdom solution) can do item 1 well, if anyone had any experience about item 2 or 3, I am all ears.

  • able to see the real dom from server is the same dom you will get by rendering client-side, thus doesn't re-render.
  • able to attach events automagically on real dom.
  • able to extract data from real dom to minimize ajax needed.

@Zolmeister
Copy link
Contributor

@bitinn for number 3 you have to manage it yourself by injecting it into the DOM (via script tag).

This is how Zorium does it:
https://github.com/Zorium/zorium-seed/blob/master/src/components/head/index.coffee#L119
https://github.com/Zorium/zorium-seed/blob/master/src/services/preload.coffee

Edit: for number 2, it already just kind of works because the first v-dom diff does it

@bitinn
Copy link
Contributor Author

bitinn commented May 6, 2015

@Zolmeister got it, so server render include inline data to be loaded by client, that's certainly a solution.

@bitinn
Copy link
Contributor Author

bitinn commented May 6, 2015

I tested vdom-virtualize a bit today with these 2 approaches:

this.vdomCache = virtualize(base);
this.nodeCache = base;
this.vdomCache = virtualize(base);
this.nodeCache = createElement(this.vdomCache);
var root = base.parentNode;
root.replaceChild(this.nodeCache, base);

Using a simple page with around 100 nodes:

On my macbook air, solution 1 takes ~15ms, solution 2 takes ~30ms.
On my iphone 6, solution 1 takes ~30ms, solution 2 takes ~45ms.

If replaceChild only add a linear penalty to render time, then replacing node might just be enough in most case.


As for events, does it mean dom-delegator is able to attach events based on diff / patch change? If we want solution 1 to work, we need to replicate create-element functionality to fire hooks?

To be honest I don't have enough knowledge about virtual-dom to reason it properly myself.


Also whichever solution I use, I am running into this bug while updating my simple page: #176

screen shot 2015-05-06 at 7 03 09 pm

If it's a virtual-dom bug, it's a blocking bug that will prevent users from doing PE.

@bitinn bitinn reopened this May 6, 2015
@alexmingoia
Copy link

That is not a bug in virtual-dom. You're creating a vdom node somewhere with contentEditable="" or there's a bug in vdom-virtualize. You need to find where in your vtree you're creating a node with contentEditable and why it has an empty value.

@bitinn
Copy link
Contributor Author

bitinn commented May 6, 2015

@alexmingoia please see this reply in that thread:

#176 (comment)

I think that's how innerHTML work, unless it's not supported by virtual-dom?

PS: we don't have anything with contentEditable in dom, and I don't see anything special with contentEditable in vdom-virtualize codebase.

@bitinn
Copy link
Contributor Author

bitinn commented May 7, 2015

@Zolmeister saw your comment edits in response to item 2 able to attach events automagically on real dom, you said this works on first vdom diff, right?

Looking at create-element source I can see it uses on apply-properties to fire hook/unhook. So who's in charge of attaching hooks (events) to real dom? If it's create-element, then I can't just create a vtree from existing dom, and expect subsequent diff/patch will auto-attach hooks right? If it's diff/patch, well, I am not sure why we need create-element to do additional work.

In short, this is key factor on whether we can just diff existing dom by getting a vtree representation of it and get event attached for free; or we should replace existing dom with result from create-element, in order to attached event properly.

@Zolmeister
Copy link
Contributor

@bitinn I don't think you understand how virtual-dom works

  1. Build new v-tree (including hooks and events)
  2. Diff v-tree from before (when constructed from DOM does not have hooks/events)
  3. Apply patch, attaching hooks and events

edit: if the DOM node already exists, you never call createElement

@bitinn
Copy link
Contributor Author

bitinn commented May 7, 2015

@Zolmeister
Copy link
Contributor

@bitinn The first time you're not diffing an existing tree, and must create the nodes

@bitinn
Copy link
Contributor Author

bitinn commented May 7, 2015

@Zolmeister

My understanding before starting this discussion is as you described:

  1. if no dom tree exists, create a new vtree then use create-element to render it in dom.
  2. if dom tree exists, get a vtree from it, and diff/patch as required, no create-element needed.
  3. if dom tree AND vtree exists, do only diff/patch.

But the discussion lead me to doubt myself :)

for example, I don't fully understand @neonstalwart's question earlier #227 (comment), if diff/patch does the magic already, what else about events do I need to worry about?

@bitinn
Copy link
Contributor Author

bitinn commented May 7, 2015

I used dom-delegator with my fix branch for vdom-virtualize now it works and correctly patch a server-rendered dom using vtree diffing and attaches events, all without using create-element.

I am closing this ticket as all 3 features from React can be implemented in some way. Thx all for replying.

@bitinn bitinn closed this as completed May 7, 2015
@bitinn
Copy link
Contributor Author

bitinn commented Jun 18, 2015

A small update: after spending much time working on progressive enhancement with virtual-dom, I finally made this module: https://github.com/bitinn/vdom-parser, it serves a similar role like vdom-virtualize and html-to-vdom, but is much better tested and target modern browsers.

We use it in production, you are welcomed to try it out.

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