Coder Social home page Coder Social logo

gabrielbull / react-router-server Goto Github PK

View Code? Open in Web Editor NEW
435.0 13.0 19.0 117 KB

Server Side Rendering library for React Router v4.

License: MIT License

JavaScript 100.00%
webpack2 react-router code-splitting server-side-rendering ssr react reactjs react-component webpack server

react-router-server's Introduction

React Router Server

Build Status Code Climate npm version Gitter

Server Side Rendering library for React Router v4.

Help wanted!

If anyone is interested in taking over this project please let me know.

Table Of Content

  1. About
  2. Installation
  3. Examples
  4. Usage
  5. API
  6. Contributing
  7. License

About

This library allows to fetch states for your components on the server side and mount them on the client side.

It also allows to do code splitting by providing a component that can be used to load modules splitted by Webpack 2.

Installation

npm install react-router-server --save

Examples

Example

A working example using Webpack bundle and preloading is provided here. To try for yourself, you can clone it and run it. This will provide a server accessible at http://localhost:3000.

git clone [email protected]:gabrielbull/react-router-server-complex-example.git
npm install
npm start

Usage

Server Side rendering

To render an app that has code splitting or state fetching, you need to load the modules and states required by your app before rendering. react-dom/server does not offer a function to do that, but you can use the renderToString function provided by this library. This function will return a promise that will return the rendered app once the modules and states are loaded.

import { renderToString } from 'react-router-server';
import App from './path/to/app';

renderToString(<App/>)
  .then(({ html }) => {
    // send html to client side
  });

Code Splitting

The code splitting consist of a component that you can use to load modules splitted by Webpack 2. It also allows you to get information on the modules required to render a page so that you can preload the modules before displaying the page on the client side.

To use code splitting, you will need to import the Module component and provide the System.import call inside the module property of that component. Then, you need to defined a callback function as the children of the component.

import { Module } from 'react-router-server';

<Module module={() => System.import('./Foo')}>
  {module => module ? <module.default/> : <div>Loading...</div>}
</Module>

To preload the modules on the client side, you can use the preload method and pass the modules from the server into that method.

In the following example, __INITIAL_MODULES__ would be provided by the server and rendered in the HTML document as a global variable.

import { preload } from 'react-router-server';
import { render } from 'react-dom';
import App from './path/to/app';

preload(__INITIAL_MODULES__).then(() => render(<App/>, document.getElementById('#my-app')));

You can get the modules from the renderToString function on the server side and extract them from your webpack stats by using the extractModules method. For more information on usage with webpack, check the usage with webpack part of this read me.

import { renderToString, extractModules } from 'react-router-server';
import App from './path/to/app';
import stats from './path/to/stats';

renderToString(<App/>)
  .then(({ html, modules }) => {
    modules = extractModules(modules, stats);
    // send html and modules to client side
  });

To be able to use System.import calls on the server side, you will need to install the babel-plugin-system-import-transformer plugin.

Fetch State

On the server side, you will often need to fetch data before rendering your component and then pass that data to the client side so that the components are in sync.

To fetch data for your components, use the fetchState decorator provided by this library. The fetchState decorator takes two arguments, mapStateToProps and mapActionsToProps. mapStateToProps allows you to map the state to the props of your component while mapActionsToProps allows you to map the done action to the props of your component.

import * as React from 'react';
import { fetchState } from 'react-router-server';

@fetchState(
  state => ({ message: state.message }),
  actions => ({ done: actions.done })
)
class MyComponent extends React.Component {
  componentWillMount() {
    if (!this.props.message) {
      setTimeout(() => {
        this.props.done({ message: 'Hello world!' });
      }, 10);
    }
  }

  render() {
    return (
      <div>{this.props.message}</div>
    );
  }
}

To pass that state from the server to the client, you need to wrap the client app with the ServerStateProvider and pass the state from the server into that component's state property.

In the following example, __INITIAL_STATE__ would be provided by the server and rendered in the HTML document as a global variable.

import { ServerStateProvider } from 'react-router-server';
import App from './path/to/app';

<ServerStateProvider state={__INITIAL_STATE__}>
  <App/>
</ServerStateProvider>

You can get the state from the renderToString function on the server side.

import { renderToString } from 'react-router-server';
import App from './path/to/app';

renderToString(<App/>)
  .then(({ html, state }) => {
    // send html and state to client side
  });

Usage with Webpack

You can extract the required modules per requests when running your server to pass them to the client side. This allows you to preload your modules before running the client side app. To do so, you need to get the stats from Webpack.

There are many ways to do this, but we recommend using the stats-webpack-plugin. Here's a code sample that you can add to your webpack config's plugins section. This will create a stats.json file that you can use to extract the required modules for your app.

[
  new StatsPlugin('stats.json', {
    chunkModules: true,
    exclude: [/node_modules/]
  })
]

To extract the modules, you can use the extractModules function and pass the modules provided by the renderToString as well as the stats generated by webpack. See the code splitting usage part of this documentation to learn more on code splitting.

To be able to use System.import calls on the server side, you will need to install the babel-plugin-system-import-transformer plugin.

Usage with React Router

To use with React Router v4, you can pass the Module component to the Route component of React Router.

import { Route } from 'react-router';
import { Module } from 'react-router-server';

<Route
  exact
  path="/"
  render={matchProps => (
    <Module module={() => System.import('./Foo')}>
      {module => module ? <module.default {...matchProps}/> : <div>Loading...</div>}
    </Module>
  )}
/>

Usage with Redux

If you are rehydrating state with redux instead of using ServerStateProvider, all you need is access to the done action so the server can wait for async stuff to complete. In that case, you can use the withDone decorator, which is a shorthand for fetchState(null, ({ done }) => ({ done })).

import * as React from 'react';
import { connect } from 'react-redux';
import { withDone } from 'react-router-server';
import { setMessage } from './actions';

@withDone
@connect(state => state.message, { setMessage })
class MyComponent extends React.Component {
  componentWillMount() {
    // using async actions
    const { setMessage, done } = this.props;
    setMessage('Hello world').then(done, done);
  }

  render() {
    return (
      <div>{this.props.message}</div>
    );
  }
}

For more details on usage with redux, check this boilerplate.

API

extractModules

extractModules(modules, stats)

modules: modules provided by the renderToString method.

stats: stats generated by webpack.

fetchState

fetchState(mapStateToProps, mapActionsToProps)

mapStateToProps(state): function to map the state provided by the done action to props in your component;

mapActionsToProps(actions): function to map the actions to props in your component; Currently, only the done action exists and is used when you are finished fetching props.

withDone

Shorthand for fetchState(null, ({ done }) => ({ done }))

Module

The Module component allows to do code splitting. The Module component takes these propeties:

module: a function that returns a System.import call. E.G. () => System.import('./Foo')

children: a function. E.G. {module => module ? <module.default/> : null}

preload

preload(modules)

modules: array of modules passed by the server side to the client side for preloading.

renderToString

Async version of ReactDOM.renderToString.

renderToString(element)

element: The element to render

Returns an object ({ html, state, modules }) with:

html: the rendered HTML

state: the app state provided by fetch state

modules: the app modules provided by code splitting

ServerStateProvider

The ServerStateProvider component is used for providing the server state to the client side. Provided by the state prop.

Contributing

Everyone is welcome to contribute and add more components/documentation whilst following the contributing guidelines.

License

React Router Server is licensed under The MIT License (MIT).

react-router-server's People

Contributors

diegohaz avatar gabrielbull avatar rikkit avatar tswaters avatar yormi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-router-server's Issues

endless loop using `babel-preset-env` with node >= 4

It looks like the transpile is slightly different when the preset-env picks up that the node environment supports arrow functions. The regular expressions aren't matching properly and things go haywire.

The imports before --

// unminified
function () {
  return __webpack_require__.e/* import() */(6).then(__webpack_require__.bind(null, 73));
}

// minified
function (){return l.e(6).then(l.bind(null,73))}

And after --

// unminified
() => __webpack_require__.e/* import() */(6).then(__webpack_require__.bind(null, 73))

// minified
()=>t.e(6).then(t.bind(null,73))

In the unminified case, the first loadFunc still matches but the second does not - meaning we'll keep trying to re-render indefinitely (endless loop). In the minified case, neither matches so there's no endless loop but modules comes back as empty.

Unless I'm missing something here, I think the regular expression could be greatly simplified by just matching the number inside a parenthetical before .then... i.e., /\(([0-9]*)\).then/

Then again, there's not a lot of tests describing what the expected values are going for this loadFunc.... in my limited testing, I wasn't able to get the block commented as system import or system import minimized to hit, despite the un-transpiled code looking something like....

<Switch>
{asyncRoute('/', () => System.import('./home'))}
</Switch>

Support with Rails server

Hey, I like the library a lot, but I have rails as my server bundled by webpack provided by react_on_rails gem, not express. IS there any way you can facilitate that with an example?

Redux support needed

Hi
this is awesome project, but it definitely needs redux support.
Is this on the road-map?

renderToString promise not resolved after upgrading to 1.1.1

Hi,

I upgraded from v0.4.0 to v1.1.1. If I try to render a component without @fetchState decorator, the rendering seems to work fine. However, when I wrap even a simple component in @fetchState decorator, calling the function this.props.done doesn't work.

For example -
This works:

import React from 'react';
class ServerRootContainer extends React.PureComponent {
  render() {
    return <h1>Rendered</h1>
  }
}
export default ServerRootContainer;

But this doesn't:

import React from 'react';
import { fetchState } from 'react-router-server';
@fetchState(
  state => ({}),
  actions => ({ done: actions.done })
)
  class ServerRootContainer extends React.PureComponent {
    componentWillMount() {
      this.props.done({});
    }
    render() {
      return <h1>Rendered</h1>
    }
  }
export default ServerRootContainer;

I see in your example that you're building the server code as well, is this necessary? I can live without the code splitting.

My .babelrc file:

{
  "presets": ["es2015", "stage-0", "react"],
  "plugins": [
   "transform-decorators-legacy",
    "add-module-exports",
    ["transform-runtime", {
      "polyfill": false,
      "regenerator": true
    }]
  ]
}

Upgrade to router v4 beta ?

Hi, i'm looking forward to use this library for a big project but i'm waiting for the api to stabilise a little bit. According to this ctrlplusb/react-universally#356 , it seems the react router api should now be stable. Is there any plan to upgrade react-router-server to react-router beta ? Many thanks.

fetchState not working if component uses contextTypes

I tried using react-router-server in a project that uses react-intl. In some components I need to use the intl context that is provided by react-intl but if I declare contextTypes for that component fetchState doesn't work.

I added console.log statements to the callback of my data fetch method and the callback of renderToString and it seems like the component just ignores that it should fetch data and renders immediately.

publicPath is ignored

In my project I have all JavaScript files in a js-Folder so I have publicPath: '/js/' in my webpack config. But when I use react-router-server for code splitting it assumes that all modules are located in the top level.

Simplify fetchState

I'm using react-router-server with redux and that's working like a charm. Thank you very much for this package.

But I always need to write this on my containers:

fetchState(null, actions => actions)(
  connect(mapStateToProps, mapDispatchToProps)(Container)
)

Since I'm rehydrating state through redux, if I got it right, the only thing I need is the done action so I can pass it to my redux actions and fetch data on server properly.

For that case, I was wondering if could be there something like withDone decorator, which just passes the done action to props:

import { withDone } from 'react-router-server'
...
withDone(connect(mapStateToProps, mapDispatchToProps)(Container))

callstack exceeded results in server crash

This might be the result of doing stupid things and using react-router in unexpected ways, but I found a mysterious crash that wound up being max callstack exceeded. You can see this if you take the react-router-server-complex-example project and make the following changes:

  • Remove the <Switch /> element from ./components/App.jsx
  • Change NoMatch to use async loading like the other routes.

If you visit one route, it works fine... but visit another and hit refresh you'll get a server crash.

On my machine, Windows10 there is no actual error returned from the process.... but you can inspect %ERRORLEVEL% to see what the exit code is... here is the output from the shell,

D:\Code\react-router-server-complex-example>node_modules\.bin\babel-node.cmd --plugins system-import-transformer -- src/server.js
Warning: Accessing PropTypes via the main React package is deprecated. Use the prop-types package from npm instead.
Example site listening on 3000!

^--- server crashes :(

D:\Code\react-router-server-complex-example>echo %ERRORLEVEL%
-1073741571

-1073741571 is a stack overflow exception.

I put a debugger on it in my own project where I found this initially, and I found that context.callback kept getting called, and went into another renderPass which ended up calling context.callback again.

I noticed the then callback in load was never fired, so add never gets called and the exists check never passes and the modules array kept getting the same module added over & over again.

not working with CommonChunksPlugin with async chunk

I'm getting an endless loop when using the async option on CommonChunksPlugin.

This config will create a chunk file for common pieces spread throughout split code. If you use the same common components on multiple split pages, it will place the goods in this chunk file.

It seems that the module loading never completes and it goes into an endless loop with modulesLoading always set to 1. This can be seen by making the following changes to react-router-server-complex-example:

diff --git a/src/components/About.jsx b/src/components/About.jsx
index d7bae53..a1a902b 100644
--- a/src/components/About.jsx
+++ b/src/components/About.jsx
@@ -1,4 +1,7 @@
 import * as React from 'react';
+import hello from './Common';
+
+hello()

 const Component = (props) => (
   <div>
diff --git a/src/components/Common.js b/src/components/Common.js
new file mode 100644
index 0000000..f08ec80
--- /dev/null
+++ b/src/components/Common.js
@@ -0,0 +1,4 @@
+
+export default () => {
+  console.log('hello world!')
+}
diff --git a/src/components/Home.jsx b/src/components/Home.jsx
index 0ad06d4..a3fe950 100644
--- a/src/components/Home.jsx
+++ b/src/components/Home.jsx
@@ -1,6 +1,9 @@
 import React, { Component, PropTypes } from 'react';
 import { fetchState } from 'react-router-server';
 import '../styles/home.css';
+import hello from './Common';
+
+hello()

 @fetchState(
   state => ({
diff --git a/src/components/Images.jsx b/src/components/Images.jsx
index a97e338..9ca0563 100644
--- a/src/components/Images.jsx
+++ b/src/components/Images.jsx
@@ -1,4 +1,7 @@
 import * as React from 'react';
+import hello from './Common';
+
+hello()

 const Images = (props) => (
   <div>
diff --git a/webpack.config.js b/webpack.config.js
index d367e06..a9bef18 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -2,6 +2,7 @@ import path from 'path';
 import StatsPlugin from 'stats-webpack-plugin';
 import fs from 'fs';
 import ExtractTextPlugin from 'extract-text-webpack-plugin';
+import webpack from 'webpack'

 const nodeModules = {};
 fs.readdirSync(path.join(__dirname, 'node_modules'))
@@ -47,6 +48,10 @@ const config = server => ({
   },

   plugins: [
+    new webpack.optimize.CommonsChunkPlugin({
+      async: 'common',
+      minChunks: 2
+    }),
     new StatsPlugin('stats.json', {
       chunkModules: true,
       exclude: [/node_modules/]

Visiting any page that references the common chunk will spin indefinitely.

renderToStaticMarkup

Hello,

Great project! Thanks!

How can we render output with React.renderToStaticMarkup ?

Warning: setState called on an unmounted component

Hi there !

First of all, thanks for your work !

I got an issue with it though and I was wondering how you would like to solve it.

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the _class component.

The faulty setState is in fact caused by:
https://github.com/gabrielbull/react-router-server/blob/master/src/fetchState.js#L60

I guess we should follow what's in here: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html

What do you think ?

should `this.idx` be `this.index` ?

I'm working on a higher order component that wraps fetchState. You can see it here:

https://github.com/tswaters/react-todo/blob/e0e539ae21528dfac07d1fdf24458012f0861a83/src/common/initial-data.jsx

The idea here is to load localization values a component needs in addition to any promises that need to be resolved prior to rendering.... I noticed a problem with this approach - I would sometimes get ready set to true without firing done, and no locale key found would show up.

(Initially, I tried to fix this by adding some kind of uniqueness to the component, so instead of a generic ready I actually used the name of the component as the key for done and for checking. This seemed to work initially, but has a flaw if multiples of the same component are in use - only the first is rendered.... (see this revision))

Anyway, inspecting the props of the component at the time of componentWillMount I found that I was getting the wrong data context the done was fired with. I tested this by including the name of the component with the ready boolean - and in some cases, the name as completely different than what should have been loading. This results in the component thinking it's already loaded and not going further.

I had a line to test this inside the componentWillMount

if (this.props.isDone && name !== this.props.name) {
  console.log('wtf?!')
}

So, I went to randomly digging around the react-router-server code and I came across this line:

reactRouterServerFetchStateParentIndex: this.idx

Here's the thing, this.idx is undefined always here. There is a this.index property. I tried toggling it to test it out and... everything works properly.

Is this a typo?

Redux integration

Hi, could you provide an example of how to use your package alongside Redux?

How to handle multiple nested redux promises ?

Hi,
How to handle multiple nested actions ??
my code is like this.

var _this = this;
    this.props.fetchStateId(this.props.match.params.name,function(err,res){
      _this.props.done();
      if(res && res.data && res.data.states_id){
        _this.props.fetchStateInfo(res.data.states_id,function(err,res){
          _this.props.done();
        });
      }
    })

i tried to call multiple done() but not worked. worked first time only.

unhandled promise rejection... can't be caught

I encountered this serendipitously while using react-helmet with a <FormattedMessage/> from react-intl.... react-helmet doesn't like this at all, and will throw back an error if it doesn't get a simple string.

Anyway, using react-router-server there is an unhandled promise rejection logged to the console. I attempted to add a catch block to the renderToString call, but this has no effect.

It appears there needs to be a try{}catch{} wrapping renderToString here -- https://github.com/gabrielbull/react-router-server/blob/master/src/renderer/renderPass.js#L36-L40 -- and if an error occurs, call into context.reject with the error.

This might also crop up with things like invariant errors or who knows what else. When it happens, the browser is left hanging with a pending request.

Preact support needed

Hi
this is awesome project and targeted at high performance apps it would be great if it would support preact.
Is this on the road-map?

React-Universal-Component/React-Async-Component/React-Loadable

Hi @gabrielbull, thank you for creating this library! I am looking to try it in a library which uses the libraries mentioned for 'asynccomponents for code-splitting. I noticed that this library has an export called` as well.

So, I am curious if you think this library would be compatible alongside those libraries if I don't use the <Module /> component or would the <Module /> component need to replace those libraries? And if replace, I'd love to understand the differences between these set of libraries and <Module /> components from you - pros/cons/tradeoffs?

Thank you!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.