decred / politeia

ISC License
110 stars 75 forks source link

[www] Add JS-less mode #839

Closed marcopeereboom closed 4 years ago

marcopeereboom commented 5 years ago

Due to the security features of Pi we were forced to use Javascript. That said, no one should be required to use Javascript to read proposals, comments and vote results. We need to add a server-side HTML generator using templates and generate read-only pages for the folks that don't need to be logged in. We need to enumerate what pages to display but anything that requires the user to be logged in in straight out and will never be supported. All other content is fair game.

It would be nice if the user can switch back and forth but I am pretty sure we can't reuse the same routes.

@behindtext suggested to go over all screens and determine what can be done strictly with HTML and replace those pages with HTML only content.

marcopeereboom commented 5 years ago

The way we should build this is by lazily render the HTML page on first contact.

The rub is dynamic pages. We need to determine if we want new comments to be rendered automatically or if we require a reload. If we pick dynamic we need JS+websockets. If we can live with human refresh we can toss JS.

For non-JS pages the flow should be as follows:

Request comes in ->
  check cached page version ->
  if it is the same version as the current state then return that to the user.
Request comes in ->
  check cached page version ->
  if it is *NOT* the same version as the current state than render new HTML +
    increase version +
    store new version in cache +
    return HTML to user
go1dfish commented 5 years ago

This is already fairly well established practice in react land.

You can have react render the same app we send users on the server side (excluding dynamic elements as desired)

With this you can re-use the same routes, and the same UI code, if JS is available it will take over rendering in the browser replacing the server content. (You can also potentially get fancier with it and serialize the data necessary to generate the page so that the UI doesn't have to hit the API again for the same content to render leading to a faster experience for everyone)

It will require some changes to the way routes are defined to help with preloading.

The serverside react render is totally synchronous and wont invoke any async behavior of the app; so you have to have all the data necessary for rendering available for it to render in a single go

I recommend that this be deferred until https://github.com/decred/politeiagui/issues/990 is completed (Not the GraphQL bit) and we aren't on redux as this will affect the server code to preload api state for rendering.

The server running the app will need to be able to access the politiea http api as if it were an outside user, I don't expect we're doing any permission grants just because something is coming from local host; so will just need to make sure the domain resolves properly and isn't somehow blocked

For cacheing, I'd let nginx handle that and have the release process reset/clear that cache; alternately there is express middleware for doing this sort of cacheing in memory or backed by redis if we wanted to get fancier with cache invalidation logic.

fernandoabolafio commented 5 years ago

I want to clarify this discussion as it can affect some decisions for the currently refactoring of Politeiagui. I've done some research on it and just by using SSR with React won't prevent us from serving javascript content. The common cases where React + SSR are used are to improve SEO, performance and things like that. Not to serve static content without Javascript. I might be wrong here but what I think @marcopeereboom is suggesting is that the Politeia server generates the HTML page and if we detect that the browser doesn't have JS support, we delegate the handling of routes and content to the Politeia server directly.

marcopeereboom commented 5 years ago

That is correct @fernandoabolafio. We want less JS not more. And seo can suck an egg.

fernandoabolafio commented 5 years ago

I have been thinking about that and a simple way to accomplish it is to have a meta tag inside a noscript tag in our index file:

<noscript>
     <meta http-equiv="refresh" content="2;url=<js_less_url>" />
     <div>Some content to be displayed while redirecting</div>
</noscript>

This will redirect to some path like https://proposals.decred.org/nojs/proposals which is handled directly by the server.

As reference what Twitter does when JS is not available is to display a form asking the user if he wants or not to proceed to the nojs version of the website:

<noscript>
      <form action="https://mobile.twitter.com/i/nojs_router?path=%2F" method="POST" class="NoScriptForm">
        <input type="hidden" value="de0d1ebd3b7399a87f5c3173adad4fb1b1392546" name="authenticity_token">

        <div class="NoScriptForm-content">
          <span class="NoScriptForm-logo Icon Icon--logo Icon--extraLarge"></span>
          <p>We've detected that JavaScript is disabled in your browser. Would you like to proceed to legacy Twitter?</p>
          <p class="NoScriptForm-buttonContainer"><button type="submit" class="EdgeButton EdgeButton--primary">Yes</button></p>
        </div>
      </form>
</noscript>
go1dfish commented 5 years ago

SSR generates usable HTML pages that are perfectly readable without JS and you can have functionality that requires javascript be absent or rendered differently in the server rendered versions.

This prevents us from needing separate routes from everything and also prevents us from having to do a separate non-js version of the UI.

@marcopeereboom you can see how this works at https://notabug.io you won't be able to login post or comment, but the site is fully readable without JS. It's generated server side using the same code the client runs when you do have JS.

@fernandoabolafio Browsers with JS disabled won't download the JS and only render the HTML sent from the server.

The JS becomes a progressive enhancement, it's more commonly done for performance and SEO because most businesses don't care about non-JS support (and are even actively hostile to it) but it's quite capable of being used to provide a JS free experience that progressively enhances when JS is available.

lukebp commented 5 years ago

@fernandoabolafio: That was my thinking as well. Detect whether the browser is using js and then use the nojs routes accordingly.

marcopeereboom commented 5 years ago

I am not ok with running a bunch of crap code server side. What I am suggesting is that we replace all pages that can be rendered serverside with templates and those are cached. Running node server side makes me queasy. @dajohi what are your thoughts?

go1dfish commented 5 years ago

Well either way, you need a way to generate the static pages, you could still use the same approach (build for SSR) and then use the node app as a static site generator that runs periodically and writes the output to different routes. This still prevents us from having to rewrite a parallel static version of the site; and this code could work for either approach.

I personally think this is needlessly convoluted and complicated (both for our implementation and users who will have twice as many potential URLs to share) than the more standard SSR approach; but it can work.

Edit: also it should be noted that if this does run as a SSR app there is no requirement that it runs on the same machine as politeiad, it's still just a client and nothing more even when running on the server.

fernandoabolafio commented 5 years ago

I am convinced that @go1dfish approach works. He showed me some examples of the code he wrote for another web app which looks pretty straightforward. This also prevents us from maintaining different views server side. We serve the app (politeiagui) using SSR trough a node server and hide/show routes and components accordingly to it's Javascript compatibility.

lukebp commented 5 years ago

We don't want to have to run a node server just to serve static pages. That's a hard no. We also don't want this functionality to be dependent on using a specific frontend framework. Generating the static pages using serverside templates is the way forward.

go1dfish commented 5 years ago

What I suggested does not require running a node server to serve static pages, it is to use node as the static page generator rather than rebuilding a parallel implementation for the static site.

Nothing about the generated html will require any framework or JS at all once it is generated.

lukebp commented 5 years ago

What I suggested does not require running a node server to serve static pages, it is to use node as the static page generator rather than rebuilding a parallel implementation for the static site.

The ability to serve static content is still dependent on running a node server though.

Nothing about the generated html will require any framework or JS at all once it is generated.

That's not what I meant. The node server uses the react api to generate the html, so your ability to generate the static content is dependent on using react.

I understand your arguments and I disagree. We're not going to run a node server.

lukebp commented 4 years ago

This has been implemented by @degeri.