marionettejs / backbone.marionette

The Backbone Framework
https://marionettejs.com
Other
7.07k stars 1.26k forks source link

Isomorphic Marionette? #2002

Closed xbill82 closed 8 years ago

xbill82 commented 9 years ago

Hello guys, I'm a great fan of Marionette, I've developed some applications on it and I'm completely convinced by it. I really love it. But, as a client-side MVC framework, it suffers the problems that are related to client-side MVC frameworks in general (basically, first-time page-load speed and SEO). Existing solutions (headless proxies, basically) duplicate the code and aren't completely satisfactory, so this "thing" called isomorphic JS is buzzing more and more on the net. Nobody really has a production solution yet, even if Yahoo posted some interesting examples using Flux and React, and AirBnb claims that they have a non-free CoffeeScript implementation of an isomorphic Backbone that they run on their production servers.

My question is: do you guys estimate it would be worth the effort to start a discussion about whether and how Marionette can be adapted to the isomorphic pattern?

mrichard commented 9 years ago

would love to see this. Been thinking of giving it a go myself to see how far I get. Thinking WalmartLabs - LazoJS could be a good start.

samccone commented 9 years ago

@mrichard @xbill82

I think a good starting place would be here https://github.com/rendrjs/rendr

I think this is possible and would only require a few DOM specific changes for this to work.

xbill82 commented 9 years ago

@samccone, great to know Rendr is actually open source! I'll take a look at it as soon as possible and try it out.

samccone commented 9 years ago

@xbill82 are you interested in investigating this further?

samccone commented 9 years ago

cc @mrichard

samccone commented 9 years ago

https://github.com/rendrjs/rendr/issues/297 https://github.com/rendrjs/rendr/issues/63

xbill82 commented 9 years ago

Sorry guys, I've been very busy during the last months and forgot to report the findings of my investigations. The way I understand the problem is that node.js cannot handle the DOM server-side, this is clear to everyone, I guess. So, the way the folks at Airbnb solved this is pretty simple: no DOM is handled server side. The page gets rendered in one single pass. This is OK if the page is constituted by only one view: just call the Backbone.View.render() method on the template an send it to the client. Awesome. But things get more complex when it comes to nested views (something that Marionette is very good at). In Marionette we use to render the Layout template, have it into the DOM, then render a new View and attach it to the selected Region of the Layout, which is nothing more than a DOM node. But server-side you have no DOM to pick Regions from: you just have HTML expressed in plain text. So, server-side Marionette is like a fish out of the water. Down at Airbnb (I mean, in Rendrjs) they solved the problem in a way that I don't really love: they developed a Handlebars helper which picks a specified view, renders it and sticks it into the template. I haven't found any mean to dynamically nest views with this method, so I don't really like it. Please don't hesitate to notify me if I miss something!

The only viable solution to this problem, I think, is the one implemented by React.js. The virtual DOM. Do you think it would make sense to integrate this in server-side Marionette?

capricube commented 9 years ago

Hi guys,

i'm trying to share my thoughts on that topic that have risen recently when i started to create a big Marionette.js app that has serious SEO requirements. Marionette.js is from our point of view well suited to support prerendered HTML documents. Features like template-less views and the ability to attach event handler without rendering is of great value.

The approach we are currently exploring is based on domino.js (https://github.com/fgnass/domino). A very lightweight DOM implementation running in a pure node.js environment which also allows javascript files to be attached and executed. By using JADE templates client- and server-side (https://github.com/HenrikJoreteg/templatizer) we hope to be able to create coarse grained template blocks that only need the view model to be populated correctly.

has anyone had similar thoughts?

bzalasky commented 9 years ago

Kristian, I had to do something similar to attach a Marionette view to a Google Maps tooltip that had been passed static HTML. I think there is merit to this approach. I had the idea recently to do something similar with Enlive and Kioo templates in Clojure/ClojureScript. The hangup here is that I'm working with React (via Om) on this side project, and it doesn't allow you to attach a view to static HTML yet, though it's on the roadmap. I know there are other approaches for isomorphic React apps, but I was intrigued with the simplicity of this approach.

On Wed, Feb 4, 2015 at 3:56 AM, Kristian Marinkovic < notifications@github.com> wrote:

Hi guys,

i'm trying to share my thoughts on that topic that have risen recently when i started to create a big Marionette.js app that has serious SEO requirements. Marionette.js is from our point of view well suited to support prerendered HTML documents. Features like template-less views and the ability to attach event handler without rendering is of great value.

The approach we are currently exploring is based on domino.js ( https://github.com/fgnass/domino). A very lightweight DOM implementation running in a pure node.js environment which also allows javascript files to be attached and executed. By using JADE templates client- and server-side ( https://github.com/HenrikJoreteg/templatizer) we hope to be able to create coarse grained template blocks that only need the view model to be populated correctly.

has anyone had similar thoughts?

— Reply to this email directly or view it on GitHub https://github.com/marionettejs/backbone.marionette/issues/2002#issuecomment-72842819 .

nuragic commented 9 years ago

Hi! Anyone using Rendr with Marionette? :)

JSteunou commented 9 years ago

+1 I'm very interest for any experience feedback. With or without rendrjs.

JSteunou commented 9 years ago

For those interested I looked at rendrjs and I cannot see how it could works with Marionette.

But, with Backbone 1.2, it is easier to get rid of jQuery and using cheerio as in this gist example seems possible. My concern is now, how to get the app started on the front-end side without erasing all the node already present in the page, which would annihilate all the work done by the back-end.

bgaillard commented 9 years ago

Hi, I just implemented a very basic sample here https://github.com/gomoob/backbone.isomorphic with 3 sample views rendered server side :

This sample is based on jsdom or domino for the DOM. You can follow the README instructions, it takes only few minutes to start the test server.

This is just a start and I think several subjects have to be studied more :

Feel free to fork the repo to do more tests...

At GoMoob Marionette isomorphic is very important because we begin to have customers who ask us to implement JS isomorphic apps but we do not want to use React because we like Marionette so much ;-)

For now in production the best solution we found is to render pages in PHP on server side and bind template less Marionette views on the existing DOM. We do it on this web site since 2 years http://www.verygoodmoment.com and it works as expected.

eth0lo commented 9 years ago

I have been working on this idea1 for a month or so already.

I went with the suggestion of @capricube of using domino. so far so good, there's a couple of things that could be improved in order to avoid a lot of overwriting, by a lot so far I just overwrote like three methods, but I expect that this will need to happen for each view.

The main problem is that marionette asumes:

  1. Existence of DOM
  2. Existence of _
  3. Existence of Backbone
  4. Existence of jQuery

Looking at jQuery's code, their implementation of UMD, when it is node, they allow to feed the document object through a factory.

If marionette implements something on those lines, it would super easy lo allow this to happen.

1 This is a branch that I'm integrating Marionette with express render pipeline

JSteunou commented 9 years ago

Hey nice to see this question still alive. How did you manage this @eth0lo?

My concern is now, how to get the app started on the front-end side without erasing all the node already present in the page, which would annihilate all the work done by the back-end.

eth0lo commented 9 years ago

In order to stabilize the frontend after the server have done its work it need two parts:

  1. Data synchronization
  2. View synchronization

Data synchronization

Probably the most easy part, imagine that you are dealing with backbone models and collections and everything is nicely wrapped inside one of those; then the process goes around like this:

  1. Keep track of the responses of your models/collections.
  2. Send it back to the client somehow.
  3. Boot your models/collections from the bootstrapped data.

Keeping track of responses

The easiest way that I found was extending models/collections that can intercept the responses, and then keeping track of this on variable inside the model.

Basically what it does is wrapping the success option in the fetch method so it can call a method that save that back to the model/collection, and then call the success method if any.

Send it back the info to the client

I took inspiration in a couple of gems out there, mainly gon and react rails.

In short gon allows you put any data that is on the server in a variable in document, so later you can retrieve it. Let's say that you want to know in which environment your app is running, so you set a variable server side like myApp.environment, then on the browser you could do the same.

And react rails add it's props to the parent node so when its time to bootstrap the data, it know where to look for it.

My approach was to get the data saved in the previous step, and add it to global variable in the document so later could be retrieved.

There's more ways to do it but this is the fastest way that I found.

Boot your models/collections

Now this is simple, you got the data laying around somewhere on the document, is just matter of getting it back.

To do so, again I modified the fetch method of both models/collection, to look first at the bootstrapped data, if they had something parse it and return a resolved jqXHR, so everything is like it did a full fetch.

View synchronization

This is the most problematic if you want to automate everything, but a nice compromise that I found, was to render the layout in the server using a LayoutView, that makes easy to compose the different components of the application together in a single view. And then when you are in the client create multiple regions in your app, and then set to not re-render and to use the underlying DOM node. The documentation of a Marionette.Region explains how to do it. An from there on it's just a Marionette Application

Also this have the added benefit that you can "inspect" the layout for the model/collection of each region recursively to serialize them and send it back to the client.

Hope I made everything clear

JSteunou commented 9 years ago

So you have regions bind to DOM without re-rendering those, but what if you have views in those region with some ui bindings, jquery plugin, etc. Does this work?

eth0lo commented 9 years ago

Backbone have the options to provide an el while creating a new Instance; when doing so it will call delegateEvents and unDelegateEvents for you, and since Marionette.View extends Backbone.View they are gonna be called, the difference is that Marionette, re-implements delegateEvents and unDelegateEvents to be able to handle UI elements, so my first hunch would be yes.

Regarding jQuery plugins, I have not yet try it, but if you relay on onDomRefresh my hunch would be no.

JSteunou commented 9 years ago

Worth the shot. Wish I had some time to work on this :D

eth0lo commented 9 years ago

@samccone would you like to help me with some marionette's internals that I'm so use to?

capricube commented 9 years ago

Since my last post in February we did lot of progress with our isomorphic application. Let me share how and what we did since then and how we solved a few of the problems mentioned above.

Server side

We created a isomorphic middleware for express.js to pre-render parts of our page. For our sitemaps we pre-render the whole page but for regular users we only pre-render the parts of the page that are above the fold (ATF). Those parts are dynamically added Regions and via a parameter we can choose how many Regions to pre-render. We also switched from Domino to JSDOM after they removed the dependency on contextify.

Data/View synchronization

After the client received the HTML with the pre-rendered ATF Regions we re-render the whole app on the client-side using a documentFragment. This way we have all the event handler correctly applied and can have it active for the user by just replacing a single root node (regardless of jquery or other libraries). Now we have the problem that the model we used for server-side pre-rendering might have changed on client-side re-rendering.

Therefore we changed our Model and Collection sync function to save the data into a cache when it is used server-side. The cache is just a <script type="application/json"> tag with all the data. On client-side re-rendering we first check whether the cache script exists, use this data and remove the cache afterwards.

I hope i could help with my input. I'm glad to see more people in exploring isomorphic Marionette.js applications.

As a side note: we are using SystemJS to load and bundle our modules and we are quite impressed. We use it to also inline CSS into our pre-rendered HTML page. But only the CSS that is actually being used by our pre-rendered regions! This way we are able to reach a extremely low Speedindex :smile: .

eth0lo commented 9 years ago

@capricube the data sync is exactly how I approached that problem.

Other option that I considered, but discarded, was creating the data as in memory file that could be requested by the client, but then became a little harder to mix that with browserify, which is the bundler that I'm using

ahumphreys87 commented 8 years ago

Going to close this as it isn't really something Marionette will provide out of the box. Its likely to form some sort of plugin.

As an aside, we have been working on this for the Orchestra project: https://github.com/BedeGaming/orchestra/blob/iso/src/node.js

It still needs work but once its ready Ill post in the marionette gitter channel.

JSteunou commented 8 years ago

err... more a github issue than marionette, but I liked having this ticket to discuss about solution, not as could be baked by marionette, but which plugin people found and deserve a look at.

Just discussion and sharing :bouquet:

paulfalgout commented 8 years ago

Yeah.. we really should do https://github.com/marionettejs/backbone.marionette/issues/2483

laurentdebricon commented 8 years ago

Hello i'm about to go deep into this issue for SEO purpose. If you have updates of what you all wrote from now nearly a year ago or fresh code for SEO back-end rendering willing to share ... (Because marionnette is still awesome for me : https://www.autovisual.com/fr/voiture-occasion/Audi/A5/all/fr--21000--Dijon/vignettes This is my 3 years of work project expressjs marionnettes d3js, a second hand car market analyser to find good deals). I was doing rendering with escaped_fragment with Fantomjs for Google then try to stop because Google depreciated it but i have high doubt Google is rendering my JS so i'm here now

Cheers all

ahumphreys87 commented 8 years ago

I actually think google renders JS now, been doing a lot of investigation into this myself and using webmaster tools Google picks up the content rendered by JS

On Tuesday, May 3, 2016, Laurent Debricon notifications@github.com wrote:

Hello i'm about to go deep into this issue for SEO purpose. If you have updates of what you all wrote from now nearly a year ago or fresh code for SEO back-end rendering willing to share ... (Because marionnette is still awesome for me : https://www.autovisual.com/fr/voiture-occasion/Audi/A5/all/fr--21000--Dijon/vignettes This is my 3 years of work project expressjs marionnettes d3js, a second hand car market analyser to find good deals). I was doing rendering with escaped_fragment with Fantomjs for Google then try to stop because Google depreciated it but i have high doubt Google is rendering my JS so i'm here now

Cheers all

— You are receiving this because you modified the open/close state. Reply to this email directly or view it on GitHub https://github.com/marionettejs/backbone.marionette/issues/2002#issuecomment-216451192