• This repository has been archived on 09/Oct/2023
  • Stars
    star
    5,824
  • Rank 6,602 (Top 0.2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created almost 8 years ago
  • Updated 7 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A service for server-side rendering your JavaScript views

Hypernova

A service for server-side rendering your JavaScript views

Join the chat at https://gitter.im/airbnb/hypernova

NPM version Build Status Dependency Status

Why?

First and foremost, server-side rendering is a better user experience compared to just client-side rendering. The user gets the content faster, the webpage is more accessible when JS fails or is disabled, and search engines have an easier time indexing it.

Secondly, it provides a better developer experience. Writing the same markup twice both on the server in your preferred templating library and in JavaScript can be tedious and hard to maintain. Hypernova lets you write all of your view code in a single place without having to sacrifice the user’s experience.

How?

Diagram that visually explains how hypernova works

  1. A user requests a page on your server.
  2. Your server then gathers all the data it needs to render the page.
  3. Your server uses a Hypernova client to submit an HTTP request to a Hypernova server.
  4. Hypernova server computes all the views into an HTML string and sends them back to the client.
  5. Your server then sends down the markup plus the JavaScript to the browser.
  6. On the browser, JavaScript is used to progressively enhance the application and make it dynamic.

Terminology

  • hypernova/server - Service that accepts data via HTTP request and responds with HTML.
  • hypernova - The universal component that takes care of turning your view into the HTML structure it needs to server-render. On the browser it bootstraps the server-rendered markup and runs it.
  • hypernova-${client} - This can be something like hypernova-ruby or hypernova-node. It is the client which gives your application the superpower of querying Hypernova and understanding how to fallback to client-rendering in case there is a failure.

Get Started

First you’ll need to install a few packages: the server, the browser component, and the client. For development purposes it is recommended to install either alongside the code you wish to server-render or in the same application.

From here on out we’ll assume you’re using hypernova-ruby and React with hypernova-react.

Node

npm install hypernova --save

This package contains both the server and the client.

Next, lets configure the development server. To keep things simple we can put the configuration in your root folder, it can be named something like hypernova.js.

var hypernova = require('hypernova/server');

hypernova({
  devMode: true,

  getComponent(name) {
    if (name === 'MyComponent.js') {
      return require('./app/assets/javascripts/MyComponent.js');
    }
    return null;
  },

  port: 3030,
});

Only the getComponent function is required for Hypernova. All other configuration options are optional. Notes on getComponent can be found below.

We can run this server by starting it up with node.

node hypernova.js

If all goes well you should see a message that says "Connected". If there is an issue, a stack trace should appear in stderr.

Rails

If your server code is written in a language other than Ruby, then you can build your own client for Hypernova. A spec exists and details on how clients should function as well as fall-back in case of failure.

Add this line to your application’s Gemfile:

gem 'hypernova'

And then execute:

$ bundle

Or install it yourself as:

$ gem install hypernova

Now lets add support on the Rails side for Hypernova. First, we’ll need to create an initializer.

config/initializers/hypernova_initializer.rb

Hypernova.configure do |config|
  config.host = "localhost"
  config.port = 3030            # The port where the node service is listening
end

In your controller, you’ll need an :around_filter so you can opt into Hypernova rendering of view partials.

class SampleController < ApplicationController
  around_filter :hypernova_render_support
end

And then in your view we render_react_component.

<%= render_react_component('MyComponent.js', :name => 'Hypernova The Renderer') %>

JavaScript

Finally, lets set up MyComponent.js to be server-rendered. We will be using React to render.

const React = require('react');
const renderReact = require('hypernova-react').renderReact;

function MyComponent(props) {
  return <div>Hello, {props.name}!</div>;
}

module.exports = renderReact('MyComponent.js', MyComponent);

Visit the page and you should see your React component has been server-rendered. If you’d like to confirm, you can view the source of the page and look for data-hypernova-key. If you see a div filled with HTML then your component was server-rendered, if the div is empty then there was a problem and your component was client-rendered as a fall-back strategy.

If the div was empty, you can check stderr where you’re running the node service.

Debugging

The developer plugin for hypernova-ruby is useful for debugging issues with Hypernova and why it falls back to client-rendering. It’ll display a warning plus a stack trace on the page whenever a component fails to render server-side.

You can install the developer plugin in examples/simple/config/environments/development.rb

require 'hypernova'
require 'hypernova/plugins/development_mode_plugin'

Hypernova.add_plugin!(DevelopmentModePlugin.new)

You can also check the output of the server. The server outputs to stdout and stderr so if there is an error, check the process where you ran node hypernova.js and you should see the error.

Deploying

The recommended approach is running two separate servers, one that contains your server code and another that contains the Hypernova service. You’ll need to deploy the JavaScript code to the server that contains the Hypernova service as well.

Depending on how you have getComponent configured, you might need to restart your Hypernova service on every deploy. If getComponent caches any code then a restart is paramount so that Hypernova receives the new changes. Caching is recommended because it helps speed up the service.

FAQ

Isn’t sending an HTTP request slow?

There isn’t a lot of overhead or latency, especially if you keep the servers in close proximity to each other. It’s as fast as compiling many ERB templates and gives you the benefit of unifying your view code.

Why not an in-memory JS VM?

This is a valid option. If you’re looking for a siloed experience where the JS service is kept separate, then Hypernova is right for you. This approach also lends itself better to environments that don’t already have a JS VM available.

What if the server blows up?

If something bad happens while Hypernova is attempting to server-render your components it’ll default to failure mode where your page will be client-rendered instead. While this is a comfortable safety net, the goal is to server-render every request.

Pitfalls

These are pitfalls of server-rendering JavaScript code and are not specific to Hypernova.

  • You’ll want to do any DOM-related manipulations in componentDidMount. componentDidMount runs on the browser but not the server, which means it’s safe to put DOM logic in there. Putting logic outside of the component, in the constructor, or in componentWillMount will cause the code to fail since the DOM isn’t present on the server.

  • It is recommended that you run your code in a VM sandbox so that requests get a fresh new JavaScript environment. In the event that you decide not to use a VM, you should be aware that singleton patterns and globals run the risk of leaking memory and/or leaking data between requests. If you use createGetComponent you’ll get VM by default.

Clients

See clients.md

Browser

The included browser package is a barebones helper which renders markup on the server and then loads it on the browser.

List of compatible browser packages:

Server

Starting up a Hypernova server

const hypernova = require('hypernova/server');

hypernova({
  getComponent: require,
});

Options, and their defaults

{
  // the limit at which body parser will throw
  bodyParser: {
    limit: 1024 * 1000,
  },
  // runs on a single process
  devMode: false,
  // how components will be retrieved,
  getComponent: undefined,
  // if not overridden, default will return the number of reported cpus  - 1
  getCPUs: undefined,
  // the host the app will bind to
  host: '0.0.0.0',
  // configure the default winston logger
  logger: {},
  // logger instance to use instead of the default winston logger
  loggerInstance: undefined,
  // the port the app will start on
  port: 8080,
  // default endpoint path
  endpoint: '/batch',
  // whether jobs in a batch are processed concurrently
  processJobsConcurrently: true,
  // arguments for server.listen, by default set to the configured [port, host]
  listenArgs: null,
  // default function to create an express app
  createApplication: () => express()
}

getComponent

This lets you provide your own implementation on how components are retrieved.

The most common use-case would be to use a VM to keep each module sandboxed between requests. You can use createGetComponent from Hypernova to retrieve a getComponent function that does this.

createGetComponent receives an Object whose keys are the component’s registered name and the value is the absolute path to the component.

const path = require('path');

hypernova({
  getComponent: createGetComponent({
    MyComponent: path.resolve(path.join('app', 'assets', 'javascripts', 'MyComponent.js')),
  }),
});

The simplest getComponent would be to use require. One drawback here is that your components would be cached between requests and thus could leak memory and/or data. Another drawback is that the files would have to exist relative to where this require is being used.

hypernova({
  getComponent: require,
});

You can also fetch components asynchronously if you wish, and/or cache them. Just return a Promise from getComponent.

hypernova({
  getComponent(name) {
    return promiseFetch('https://MyComponent');
  },
});

getCPUs

This lets you specify the number of cores Hypernova will run workers on. Receives an argument containing the number of cores as reported by the OS.

If this method is not overridden, or if a falsy value is passed, the default method will return the number of reported cores minus 1.

loggerInstance

This lets you provide your own implementation of a logger as long as it has a log() method.

const winston = require('winston');
const options = {};

hypernova({
  loggerInstance: new winston.Logger({
        transports: [
          new winston.transports.Console(options),
        ],
      }),
});

processJobsConcurrently

This determines whether jobs in a batch are processed concurrently or serially. Serial execution is preferable if you use a renderer that is CPU bound and your plugins do not perform IO in the per job hooks.

createApplication

This lets you provide your own function that creates an express app. You are able to add your own express stuff like more routes, middlewares, etc. Notice that you must pass a function that returns an express app without calling the listen method!

const express = require('express');
const yourOwnAwesomeMiddleware = require('custom-middleware');

hypernova({
  createApplication: function() {
    const app = express();
    app.use(yourOwnAwesomeMiddleware);

    app.get('/health', function(req, res) {
      return res.status(200).send('OK');
    });

    // this is mandatory.
    return app;
  }

API

Browser

load

type DeserializedData = { [x: string]: any };
type ServerRenderedPair = { node: HTMLElement, data: DeserializedData };

function load(name: string): Array<ServerRenderedPair> {}

Looks up the server-rendered DOM markup and its corresponding script JSON payload and returns it.

serialize

type DeserializedData = { [x: string]: any };

function serialize(name: string, html: string, data: DeserializedData): string {}

Generates the markup that the browser will need to bootstrap your view on the browser.

toScript

type DeserializedData = { [x: string]: any };
type Attributes = { [x: string]: string };

function toScript(attrs: Attributes, props: DeserializedData): string {}

An interface that allows you to create extra script tags for loading more data on the browser.

fromScript

type DeserializedData = { [x: string]: any };
type Attributes = { [x: string]: string };

function fromScript(attrs: Attributes): DeserializedData {}

The inverse of toScript, this function runs on the browser and attempts to find and JSON.parse the contents of the server generated script. attrs is an object where the key will be a data-key to be placed on the element, and the value is the data attribute's value.

The serialize function uses the attributes DATA_KEY and DATA_ID to generate the data markup. They can be used in the fromScript function to get the serialized data.

import { DATA_KEY, DATA_ID } from 'hypernova'

fromScript({
    [DATA_KEY]: key,
    [DATA_ID]: id,
 });

Server

createGetComponent

type Files = { [key: string]: string };
type VMOptions = { cacheSize: number, environment?: () => any };
type GetComponent = (name: string) => any;

function createGetComponent(files: Files, vmOptions: VMOptions): GetComponent {}

Creates a getComponent function which can then be passed into Hypernova so it knows how to retrieve your components. createGetComponent will create a VM so all your bundles can run independently from each other on each request so they don’t interfere with global state. Each component is also cached at startup in order to help speed up run time. The files Object key is the component’s name and its value is the absolute path to the component.

createVM

type VMOptions = { cacheSize: number, environment?: () => any };
type Run = (name: string, code: string) => any;
type VMContainer = { exportsCache: any, run: Run };

function createVM(options: VMOptions): VMContainer {}

Creates a VM using Node’s vm module. Calling run will run the provided code and return its module.exports. exportsCache is an instance of lru-cache.

getFiles

function getFiles(fullPathStr: string): Array<{name: string, path: string}> {}

A utility function that allows you to retrieve all JS files recursively given an absolute path.

Module

Module is a class that mimics Node’s module interface. It makes require relative to whatever directory it’s run against and makes sure that each JavaScript module runs in its own clean sandbox.

loadModules

function loadModules(require: any, files: Array<string>): () => Module? {}

Loads all of the provided files into a Module that can be used as a parent Module inside a VM. This utility is useful when you need to pre-load a set of shims, shams, or JavaScript files that alter the runtime context. The require parameter is Node.js’ require function.

More Repositories

1

javascript

JavaScript Style Guide
JavaScript
141,845
star
2

lottie-android

Render After Effects animations natively on Android and iOS, Web, and React Native
Java
34,600
star
3

lottie-web

Render After Effects animations natively on Web, Android and iOS, and React Native. http://airbnb.io/lottie/
JavaScript
29,564
star
4

lottie-ios

An iOS library to natively render After Effects vector animations
Swift
24,897
star
5

visx

🐯 visx | visualization components
TypeScript
18,609
star
6

react-sketchapp

render React components to Sketch ⚛️💎
TypeScript
14,951
star
7

react-dates

An easily internationalizable, mobile-friendly datepicker library for the web
JavaScript
11,630
star
8

epoxy

Epoxy is an Android library for building complex screens in a RecyclerView
Java
8,426
star
9

css

A mostly reasonable approach to CSS and Sass.
6,869
star
10

mavericks

Mavericks: Android on Autopilot
Kotlin
5,741
star
11

knowledge-repo

A next-generation curated knowledge sharing platform for data scientists and other technical professions.
Python
5,432
star
12

ts-migrate

A tool to help migrate JavaScript code quickly and conveniently to TypeScript
TypeScript
5,307
star
13

aerosolve

A machine learning package built for humans.
Scala
4,790
star
14

DeepLinkDispatch

A simple, annotation-based library for making deep link handling better on Android
Java
4,356
star
15

lottie

Lottie documentation for http://airbnb.io/lottie.
HTML
4,289
star
16

ruby

Ruby Style Guide
Ruby
3,711
star
17

polyglot.js

Give your JavaScript the ability to speak many languages.
JavaScript
3,644
star
18

MagazineLayout

A collection view layout capable of laying out views in vertically scrolling grids and lists.
Swift
3,232
star
19

native-navigation

Native navigation library for React Native applications
Java
3,127
star
20

streamalert

StreamAlert is a serverless, realtime data analysis framework which empowers you to ingest, analyze, and alert on data from any environment, using datasources and alerting logic you define.
Python
2,825
star
21

infinity

UITableViews for the web (DEPRECATED)
JavaScript
2,809
star
22

airpal

Web UI for PrestoDB.
Java
2,760
star
23

HorizonCalendar

A declarative, performant, iOS calendar UI component that supports use cases ranging from simple date pickers all the way up to fully-featured calendar apps.
Swift
2,656
star
24

swift

Airbnb's Swift Style Guide
Markdown
2,239
star
25

synapse

A transparent service discovery framework for connecting an SOA
Ruby
2,067
star
26

Showkase

🔦 Showkase is an annotation-processor based Android library that helps you organize, discover, search and visualize Jetpack Compose UI elements
Kotlin
2,018
star
27

paris

Define and apply styles to Android views programmatically
Kotlin
1,894
star
28

AirMapView

A view abstraction to provide a map user interface with various underlying map providers
Java
1,861
star
29

react-with-styles

Use CSS-in-JavaScript with themes for React without being tightly coupled to one implementation
JavaScript
1,697
star
30

rheostat

Rheostat is a www, mobile, and accessible slider component built with React
JavaScript
1,692
star
31

binaryalert

BinaryAlert: Serverless, Real-time & Retroactive Malware Detection.
Python
1,382
star
32

epoxy-ios

Epoxy is a suite of declarative UI APIs for building UIKit applications in Swift
Swift
1,142
star
33

nerve

A service registration daemon that performs health checks; companion to airbnb/synapse
Ruby
942
star
34

okreplay

📼 Record and replay OkHttp network interaction in your tests.
Groovy
775
star
35

RxGroups

Easily group RxJava Observables together and tie them to your Android Activity lifecycle
Java
693
star
36

prop-types

Custom React PropType validators that we use at Airbnb.
JavaScript
672
star
37

react-outside-click-handler

OutsideClickHandler component for React.
JavaScript
603
star
38

ResilientDecoding

This package makes your Decodable types resilient to decoding errors and allows you to inspect those errors.
Swift
580
star
39

babel-plugin-dynamic-import-node

Babel plugin to transpile import() to a deferred require(), for node
JavaScript
575
star
40

kafkat

KafkaT-ool
Ruby
504
star
41

babel-plugin-dynamic-import-webpack

Babel plugin to transpile import() to require.ensure, for Webpack
JavaScript
500
star
42

chronon

Chronon is a data platform for serving for AI/ML applications.
Scala
479
star
43

babel-plugin-inline-react-svg

A babel plugin that optimizes and inlines SVGs for your React Components.
JavaScript
474
star
44

lunar

🌗 React toolkit and design language for Airbnb open source and internal projects.
TypeScript
461
star
45

BuckSample

An example app showing how Buck can be used to build a simple iOS app.
Objective-C
459
star
46

SpinalTap

Change Data Capture (CDC) service
Java
428
star
47

artificial-adversary

🗣️ Tool to generate adversarial text examples and test machine learning models against them
Python
390
star
48

dynein

Airbnb's Open-source Distributed Delayed Job Queueing System
Java
383
star
49

hammerspace

Off-heap large object storage
Ruby
364
star
50

trebuchet

Trebuchet launches features at people
Ruby
313
star
51

reair

ReAir is a collection of easy-to-use tools for replicating tables and partitions between Hive data warehouses.
Java
278
star
52

zonify

a command line tool for generating DNS records from EC2 instances
Ruby
270
star
53

ottr

Serverless Public Key Infrastructure Framework
Python
266
star
54

omniduct

A toolkit providing a uniform interface for connecting to and extracting data from a wide variety of (potentially remote) data stores (including HDFS, Hive, Presto, MySQL, etc).
Python
249
star
55

hypernova-react

React bindings for Hypernova.
JavaScript
248
star
56

smartstack-cookbook

The chef recipes for running and testing Airbnb's SmartStack
Ruby
244
star
57

interferon

Signaling you about infrastructure or application issues
Ruby
239
star
58

prop-types-exact

For use with React PropTypes. Will error on any prop not explicitly specified.
JavaScript
237
star
59

backpack

A pack of UI components for Backbone projects. Grab your backpack and enjoy the Views.
HTML
223
star
60

babel-preset-airbnb

A babel preset for transforming your JavaScript for Airbnb
JavaScript
222
star
61

goji-js

React ❤️ Mini Program
TypeScript
213
star
62

react-with-direction

Components to provide and consume RTL or LTR direction in React
JavaScript
192
star
63

stemcell

Airbnb's EC2 instance creation and bootstrapping tool
Ruby
185
star
64

hypernova-ruby

Ruby client for Hypernova.
Ruby
141
star
65

kafka-statsd-metrics2

Send Kafka Metrics to StatsD.
Java
135
star
66

optica

A tool for keeping track of nodes in your infrastructure
Ruby
134
star
67

sparsam

Fast Thrift Bindings for Ruby
C++
125
star
68

js-shims

JS language shims used by Airbnb.
JavaScript
123
star
69

browser-shims

Browser and JS shims used by Airbnb.
JavaScript
118
star
70

bossbat

Stupid simple distributed job scheduling in node, backed by redis.
JavaScript
118
star
71

nimbus

Centralized CLI for JavaScript and TypeScript developer tools.
TypeScript
118
star
72

lottie-spm

Swift Package Manager support for Lottie, an iOS library to natively render After Effects vector animations
Ruby
106
star
73

twitter-commons-sample

A sample REST service based on Twitter Commons
Java
103
star
74

is-touch-device

Is the current JS environment a touch device?
JavaScript
90
star
75

rudolph

A serverless sync server for Santa, built on AWS
Go
73
star
76

hypernova-node

node.js client for Hypernova
JavaScript
73
star
77

plog

Fire-and-forget UDP logging service with custom Netty pipelines & extensive monitoring
Java
72
star
78

cloud-maker

Building castles in the sky
Ruby
67
star
79

react-create-hoc

Create a React Higher-Order Component (HOC) following best practices.
JavaScript
66
star
80

vulnture

Python
65
star
81

deline

An ES6 template tag that strips unwanted newlines from strings.
JavaScript
63
star
82

react-with-styles-interface-react-native

Interface to use react-with-styles with React Native
JavaScript
63
star
83

sputnik

Scala
61
star
84

mocha-wrap

Fluent pluggable interface for easily wrapping `describe` and `it` blocks in Mocha tests.
JavaScript
54
star
85

react-with-styles-interface-aphrodite

Interface to use react-with-styles with Aphrodite
JavaScript
54
star
86

eslint-plugin-react-with-styles

ESLint plugin for react-with-styles
JavaScript
49
star
87

sssp

Software distribution by way of S3 signed URLs
Haskell
47
star
88

alerts

An example alerts repo, for use with airbnb/interferon.
Ruby
46
star
89

apple-tv-auth

Example application to demonstrate how to build Apple TV style authentication.
Ruby
44
star
90

airbnb-spark-thrift

A library for loadling Thrift data into Spark SQL
Scala
43
star
91

jest-wrap

Fluent pluggable interface for easily wrapping `describe` and `it` blocks in Jest tests.
JavaScript
39
star
92

billow

Query AWS data without API credentials. Don't wait for a response.
Java
38
star
93

gosal

A Sal client written in Go
Go
36
star
94

backbone.baseview

DEPRECATED: A simple base view class for Backbone.View
JavaScript
34
star
95

anotherlens

News Deeply X Airbnb.Design - Another Lens
HTML
33
star
96

eslint-plugin-miniprogram

TypeScript
33
star
97

react-component-variations

JavaScript
33
star
98

react-with-styles-interface-css

📃 CSS interface for react-with-styles
JavaScript
31
star
99

appear

reveal terminal programs in the gui
Ruby
29
star
100

puppet-munki

Puppet
29
star