Search code examples
prerenderserver-side-renderinghyperhtml

Server Side Rendering with viperHTML


I'm working on a Symfony application and just got SSR for JS working using https://github.com/spatie/server-side-rendering. So far I only worked with "readymade" SSR solutions for React, but currently I'm trying to use hyperHTML/viperHTML and am facing a few issues that so far I wasn't able to solve by looking at the available docs/examples.

My current test snippet is this:

const viperHTML = require('viperhtml');

class Component extends viperHTML.Component {    
    constructor(props) {
        super();
        this.props = props;
    }

    render() {
        return this.html`
      <h1>Hello, ${this.props.name}</h1>`;
    }
}

console.log(new Component({ name: 'Joe' }).render().toString());

The thing here is that without explicitly calling render() I get no output. Looking at some of the official examples this shouldn't be necessary, at least not with Component. I already tried using setState() in the constructor, for example, but no difference.

Also, without using both, console.log() and toString(), I get no output either. Which is unexpected. I get that toString() might be necessary here (without it a <buffer /> is being rendered), but the console.log() seems odd. This might not be related to viperHTML at all of course. But instantiating the component is the only thing I expected to be necessary.

It's also not clear to me yet how I can write an isomorphic/universal component, i.e. one file which has the markup, event handlers etc., gets rendered on the server and then hydrated on the client. When I add an inline event handler as per the docs (https://viperhtml.js.org/hyperhtml/documentation/#essentials-6) it actually gets inlined into the rendered markup, which is not what I want. I checked hypermorphic and the viperNews app, but that didn't really help me so far.


Solution

  • In case it helps, you can read viperHTML tests to see how components can be used.

    The thing here is that without explicitly calling render() I get no output.

    Components are meant to be used to render layout, either on the server or on the client side. This means if you pass a component instance to a hyper/viperHTML view, you don't have to worry about calling anything, it's done for you.

    const {bind, Component} = require('viperhtml');
    
    class Hello extends Component {    
      constructor(props) {
        super().props = props;
      }
      render() {
        return this.html`<h1>Hello, ${this.props.name}</h1>`;
      }
    }
    
    console.log(
      // you need a hyper/viperHTML literal to render components
      bind({any:'ref'})`${Hello.for({ name: 'Joe' })}`
        // by default you have a buffer to stream in NodeJS
        // if you want a string you need to use toString()
        .toString()
    );
    

    Since NodeJS by default streams buffers, any layout produced by viperHTML will be buffers and, as such, can be streamed while it's composed (i.e. with Promises as interpolation values).

    It's also not clear to me yet how I can write an isomorphic/universal component, i.e. one file which has the markup, event handlers etc., gets rendered on the server and then hydrated on the client.

    The original version of hyperHTML had a method called adopt() which purpose was to hydrate live nodes through same template literals.

    While viperHTML has an viperhtml.adoptable = true switch to render adoptable content, hyperHTML adopt feature is still not quite there yet so that, for the time being, you can easily share views between SSR and the FE, but you need to either take over on the client once the SSR page has landed or react, for the very first time, differently and take over on the client at distance.

    This is not optimal, but I'm afraid the hydration bit, done right, is time consuming and I haven't found such time to finalize it and ship it.

    That might be hyperHTML v3 at this point.

    I hope this answer helped understanding how viperHTML works and what's the current status.