Skip to content

riot/ssr

Repository files navigation

ssr

Riot.js SSR logo

Riot module to render riot components on the server

Build Status

NPM version NPM downloads MIT License

Installation

npm i -S riot @riotjs/compiler @riotjs/ssr

Usage

render - to render only markup

You can simply render your components' markup as it follows:

import MyComponent from './my-component.js'
import render from '@riotjs/ssr'

const html = render('my-component', MyComponent, { some: 'initial props' })

Important If you want to import raw .riot components in your application you might want to use @riotjs/register

Note that components rendered on the server will always automatically receive the isServer=true property.

renderAsync - to handle asynchronous rendering

Components that can not be rendered synchronously must expose the onAsyncRendering method to the renderAsync function. For example:

<async-component>
  <p>{ state.username }<p>

  <script>
    export default {
      onBeforeMount({ isServer }) {
        // if it's not SSR we load the user data right the way
        if (!isServer) {
          this.loadUser()
        }
      },
      loadUser() {
        return fetch('/user/name').then(({name}) => {
          this.update({ name })
        })
      },
      // this function will be automatically called only
      // if the component is rendered via `renderAsync`
      onAsyncRendering() {
        return this.loadUser()
      }
    }
  </script>
</async-component>

The above component can be rendered on the server as it follows:

import MyComponent from './async-component.js'
import { renderAsync } from '@riotjs/ssr'

renderAsync('async-component', MyComponent, { some: 'initial props' }).then(
  (html) => {
    console.log(html)
  },
)

Notice that the onAsyncRendering can either return a promise or use the resolve, reject callbacks:

export default {
  // this is ok
  async onAsyncRendering() {
    await loadData()
  },
}
export default {
  // this is also ok
  onAsyncRendering(resolve, reject) {
    setTimeout(resolve, 1000)
  },
}

IMPORTANT nested onAsyncRendering on children components are not supported!

fragments - to render html and css

You can also extract the rendered html and css separately using the fragments function:

import MyComponent from './my-component.js'
import { fragments } from '@riotjs/ssr'

const { html, css } = fragments('my-component', MyComponent, {
  some: 'initial props',
})

renderAsyncFragments - to handle asynchronous fragments rendering

It works like the method above but asynchronously

Advanced tips

If you want to render your whole document you can simply pass html as name of your root node. For example

<html>
    <head>
        <title>{ state.message }</title>
        <meta each={ meta in state.meta } {...meta}/>
    </head>

    <body>
        <p>{ state.message }</p>
        <script src='path/to/a/script.js'></script>
    </body>

    <script>
        export default {
          state: {
            message: 'hello',
            meta: [{
              name: 'description',
              content: 'a description'
            }]
          }
        }
    </script>
</html>

It can be rendered as it follows:

import MyRootApplication from './my-root-application.js'
import render from '@riotjs/ssr'

const html = render('html', MyRootApplication)

Better SSR control using the createRenderer

For a better control over your HTML rendering you might want to use the createRenderer factory function. This method allows the creation of a rendering function receiving the {getHTML, css, dispose, element} option object.

  • getHTML: give you the rendered html of your component as string
  • css: the css of your component as string
  • dispose: clean the memory used on the server needed to render your component
  • element: the component instance you are mounting

For example

import MyComponent from './my-component.js'
import { createRenderer } from '@riotjs/ssr'

const logRendrer = createRenderer(({ getHTML, getCSS, dispose, component }) => {
  const html = getHTML()
  const css = getCSS()

  console.log('Rendering the component: %s', component.name)

  dispose()
  return { html, css }
})

// use your logRenderer
const { html, css } = logRendrer('my-component', MyComponent, {
  some: 'initial props',
})

DOM Globals

@riotjs/ssr needs DOM globals (like window, document ...) to properly render your markup. With the domGlobals exported object you can decide manually when the globals should be created and deleted from in your node applications.

import { domGlobals } from '@riotjs/ssr'

domGlobals.create()

// global DOM object in your node environement are now defined
console.log(global.window, global.document)

// they will be cleared and will be undefined
domGlobals.clear()

Caveat

If you are rendering your whole HTML you will not be able to use multiple times the inline <script> <style> tags. Of course, you can use only once the ones used by Riot.js to customize your components. For example:

<html>
    <head>
        <!-- allowed -->
        <script src='path/to/some/script.js'></script>

        <!-- not allowed -->
        <style>
        </style>

        <!-- not allowed -->
        <script>
            const globalstuff = {}
        </script>
    </head>

    <body>
        <!-- application html -->
    </body>

    <!-- allowed -->
    <script>
        export default {
            // app code
        }
    </script>

    <!-- allowed -->
    <style>
        :host {}
    </style>
</html>