[fix] Server-side rendering

`AppRegistry.prerenderApplication` now returns a style element for use
in app shells.

Guard use of `window` in APIs and Event plugin.

Fix #107
Fix #108
This commit is contained in:
Nicolas Gallagher
2016-03-20 11:41:41 -07:00
parent 36ea662402
commit 9b2421cdfa
7 changed files with 57 additions and 18 deletions
+5 -2
View File
@@ -16,8 +16,11 @@ into `runApplication`. These should always be used as a pair.
(web) static **prerenderApplication**(appKey:string, appParameters: object)
Renders the given application to an HTML string. Use this for server-side
rendering. Return object is of type `{ html: string; style: string; }`, where
`html` the prerendered HTML, and `style` is the prerendered style sheet.
rendering. Return object is of type `{ html: string; style: string;
styleElement: ReactComponent }`. `html` is the prerendered HTML, `style` is the
prerendered style sheet, and `styleElement` is a React Component. It's
recommended that you use `styleElement` to render the style sheet in an app
shell.
static **registerConfig**(config: Array<AppConfig>)
+5 -5
View File
@@ -33,7 +33,7 @@ React.renderToStaticMarkup(<div />)
Rendering using the `AppRegistry`:
```
```js
// App.js
import React, { AppRegistry } from 'react-native'
@@ -64,12 +64,12 @@ rendering.
import React from 'react-native'
const AppShell = (html, style) => (
const AppShell = (html, styleElement) => (
<html>
<head>
<meta charSet="utf-8" />
<meta content="initial-scale=1,width=device-width" name="viewport" />
{style}
{styleElement}
</head>
<body>
<div id="react-app" dangerouslySetInnerHTML={{ __html: html }} />
@@ -90,8 +90,8 @@ import AppShell from './AppShell'
AppRegistry.registerComponent('App', () => App)
// prerenders the app
const { html, style } = AppRegistry.prerenderApplication('App', { initialProps })
const { html, style, styleElement } = AppRegistry.prerenderApplication('App', { initialProps })
// renders the full-page markup
const renderedApplicationHTML = React.renderToString(<AppShell html={html} style={style} />)
const renderedApplicationHTML = React.renderToStaticMarkup(<AppShell html={html} styleElement={styleElement} />)
```
@@ -0,0 +1,20 @@
/* eslint-env mocha */
import assert from 'assert'
import React from 'react'
import { elementId } from '../../StyleSheet'
import { prerenderApplication } from '../renderApplication'
const component = () => <div />
suite('apis/AppRegistry/renderApplication', () => {
test('prerenderApplication', () => {
const { html, style, styleElement } = prerenderApplication(component, {})
assert.ok(html.indexOf('<div ') > -1)
assert.ok(typeof style === 'string')
assert.equal(styleElement.type, 'style')
assert.equal(styleElement.props.id, elementId)
assert.equal(styleElement.props.dangerouslySetInnerHTML.__html, style)
})
})
+6 -3
View File
@@ -13,14 +13,16 @@ import ReactDOMServer from 'react-dom/server'
import ReactNativeApp from './ReactNativeApp'
import StyleSheet from '../../apis/StyleSheet'
const renderStyleSheetToString = () => `<style id="${StyleSheet.elementId}">${StyleSheet._renderToString()}</style>`
const renderStyleSheetToString = () => StyleSheet._renderToString()
const styleAsElement = (style) => <style dangerouslySetInnerHTML={{ __html: style }} id={StyleSheet.elementId} />
const styleAsTagString = (style) => `<style id="${StyleSheet.elementId}">${style}</style>`
export default function renderApplication(RootComponent: Component, initialProps: Object, rootTag: any) {
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag)
// insert style sheet if needed
const styleElement = document.getElementById(StyleSheet.elementId)
if (!styleElement) { rootTag.insertAdjacentHTML('beforebegin', renderStyleSheetToString()) }
if (!styleElement) { rootTag.insertAdjacentHTML('beforebegin', styleAsTagString(renderStyleSheetToString())) }
const component = (
<ReactNativeApp
@@ -41,5 +43,6 @@ export function prerenderApplication(RootComponent: Component, initialProps: Obj
)
const html = ReactDOMServer.renderToString(component)
const style = renderStyleSheetToString()
return { html, style }
const styleElement = styleAsElement(style)
return { html, style, styleElement }
}
+9 -6
View File
@@ -6,20 +6,23 @@
* @flow
*/
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import invariant from 'fbjs/lib/invariant'
const win = ExecutionEnvironment.canUseDOM ? window : { screen: {} }
const dimensions = {
screen: {
fontScale: 1,
get height() { return window.screen.height },
scale: window.devicePixelRatio || 1,
get width() { return window.screen.width }
get height() { return win.screen.height },
scale: win.devicePixelRatio || 1,
get width() { return win.screen.width }
},
window: {
fontScale: 1,
get height() { return window.innerHeight },
scale: window.devicePixelRatio || 1,
get width() { return window.innerWidth }
get height() { return win.innerHeight },
scale: win.devicePixelRatio || 1,
get width() { return win.innerWidth }
}
}
+7 -1
View File
@@ -6,9 +6,15 @@
* @flow
*/
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import invariant from 'fbjs/lib/invariant'
const connection = window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection
const connection = ExecutionEnvironment.canUseDOM && (
window.navigator.connection ||
window.navigator.mozConnection ||
window.navigator.webkitConnection
)
const eventTypes = [ 'change' ]
/**
@@ -2,6 +2,7 @@
import EventConstants from 'react/lib/EventConstants'
import EventPluginRegistry from 'react/lib/EventPluginRegistry'
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import ResponderEventPlugin from 'react/lib/ResponderEventPlugin'
import ResponderTouchHistoryStore from 'react/lib/ResponderTouchHistoryStore'
import normalizeNativeEvent from './normalizeNativeEvent'
@@ -18,7 +19,10 @@ const {
topTouchStart
} = EventConstants.topLevelTypes
const supportsTouch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch
const supportsTouch = ExecutionEnvironment.canUseDOM && (
'ontouchstart' in window ||
window.DocumentTouch && document instanceof window.DocumentTouch
)
const endDependencies = supportsTouch ? [ topTouchCancel, topTouchEnd ] : [ topMouseUp ]
const moveDependencies = supportsTouch ? [ topTouchMove ] : [ topMouseMove ]