Skip to content

Commit

Permalink
Add code splitting support to RootElement (#963)
Browse files Browse the repository at this point in the history
* Added support for componentLoader on RootElement

* Added code splitting example

* Fixed isse where client transitions with reuseDom enabled was broken
  • Loading branch information
davidk107 authored and gigabo committed Oct 18, 2017
1 parent 84d1ad4 commit 7f7f16f
Show file tree
Hide file tree
Showing 16 changed files with 4,393 additions and 12 deletions.
1 change: 1 addition & 0 deletions packages/react-server-examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Installation and running instructions are in the respective README.md files:
- [**bike-share**](./bike-share): An example project for react-server which demos server rendering, interactivity on the client side, ReactServerAgent, url parameters and logging.
- [**redux-basic**](./redux-basic): An example of the official Redux Counter app powered by react-server.
- [**meteor-site**](./meteor-site): An example of react-server using Google Map, redux and transitions.
- [**code-splitting**](./code-splitting): An example showing off how to do code splitting in react-server.
3 changes: 3 additions & 0 deletions packages/react-server-examples/code-splitting/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["react-server"],
}
2 changes: 2 additions & 0 deletions packages/react-server-examples/code-splitting/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
__clientTemp
4 changes: 4 additions & 0 deletions packages/react-server-examples/code-splitting/.reactserverrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"routesFile": "./routes.js",
"webpackConfig": "./webpackConfig.js"
}
64 changes: 64 additions & 0 deletions packages/react-server-examples/code-splitting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Basic Code Splitting Example

A version of the HelloWorld page with code splitting enabled. This page has 3 parts to it, header, body, and a footer. The body is configured to be loaded on initial page load. The header will then load soon after that and with the footer loading around 6 seconds after the inital page was loaded.

This demo currently utilizes `require.ensure` to power dynamic code splitting. But in the future this can be done with `import()`.

To install:

```
git clone https://github.com/redfin/react-server.git
cd react-server/packages/react-server-examples/code-splitting
npm install
```

To start in development mode:

```
npm start
```

Then go to [localhost:3000](http://localhost:3000/). You will see a simple page that pre-renders and that is interactive on load. It also will include hot reloading of React components in their own file.

# How to do Code Splitting
`RootElement` will take an async function via the `componentLoader` prop. This function is expected to resolve to a single React component that `RootElement` will then render and pass any props from `listen`, `when`, and/or `childProps` into.

# Example Usages

This will render the component that calling `loader` resolves to.
```jsx
getElements() {
return (
<RootElement componentLoader={loader} />
);
}
```

This will render the component that calling `loader` resolves to and with `displayText` passed in as a prop to it.
```jsx
getElements() {
return (
<RootElement componentLoader={loader} childProps={{displayText: "Hello World"}}/>
);
}
```

This will render when `storeEmitter` has fired at least once and `loader` has resolved to a component. Any subsequent fires from `storeEmitter` will trigger a re-render with updated prop values.
```jsx
getElements() {
return (
<RootElement listen={storeEmitter} componentLoader={loader} />
);
}
```

This will render the resolved component with `Content` as a child of the resolved component.
```jsx
getElements() {
return (
<RootElement componentLoader={loader}>
<Content text="Hello World" />
</RootElement>
);
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Q from "q";

const _loadFooterCode = (deferred) => {
require.ensure([], function() {
const component = require("../components/Footer").default;
deferred.resolve(component);
}, "Footer");
}

// This will load the footer code when called
// This loader is configured to wait 6 seconds after it was called
// before it fires off the network request to load the chunk
const footerLoader = () => {
const deferred = Q.defer();

// eslint-disable-next-line no-process-env
if (process.env.IS_SERVER) {
const component = require("../components/Footer").default;
deferred.resolve(component);
} else {
setTimeout(_loadFooterCode.bind(null, deferred), 6000);
}

return deferred.promise;
};

export default footerLoader;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Q from "q";

// This loads the header code
// This loader is configured to fire off the request for the chunk
// as soon as the loader is called
const headerLoader = () => {
const deferred = Q.defer();

// We need this IS_SERVER check because require.ensure is not supported
// on the server. There we just use regular require to get
// the component

// eslint-disable-next-line no-process-env
if (process.env.IS_SERVER) {
const component = require("../components/Header").default;
deferred.resolve(component);
} else {
require.ensure([], function() {
const component = require("../components/Header").default;
deferred.resolve(component);
}, "Header");
}

return deferred.promise;
};

export default headerLoader;
30 changes: 30 additions & 0 deletions packages/react-server-examples/code-splitting/components/Body.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React, { Component } from 'react';
import {logging} from 'react-server';

const logger = logging.getLogger(__LOGGER__);

export default class Body extends Component {
constructor(props) {
super(props);
this.increment = () => {
this.setState({exclamationCount: this.state.exclamationCount + 1});
}

this.state = {
exclamationCount: 0,
};
}

componentDidMount() {
logger.info("body rendered");
}

render() {
return (
<div className="body">
<h2>Hello, World{"!".repeat(this.state.exclamationCount)}</h2>
<button onClick={this.increment}>Get More Excited!</button>
</div>
);
}
}
30 changes: 30 additions & 0 deletions packages/react-server-examples/code-splitting/components/Footer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React, { Component } from 'react';
import {logging} from 'react-server';

const logger = logging.getLogger(__LOGGER__);

export default class Footer extends Component {
constructor(props) {
super(props);
this.state = {
mounted: false,
};
}

componentDidMount() {
logger.info("footer rendered");
this.setState({
mounted: true,
});
}

render() {
const isMounted = this.state.mounted;
const footerText = `Footer ${isMounted ? "is loaded" : " has not been loaded"}`;
return (
<div className="footer">
{footerText}
</div>
);
}
}
14 changes: 14 additions & 0 deletions packages/react-server-examples/code-splitting/components/Header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import {logging} from 'react-server';

const logger = logging.getLogger(__LOGGER__);

export default ({ headerText }) => {
logger.info("rendering the header");

return (
<div className="header">
React-Server { headerText }
</div>
);
}
24 changes: 24 additions & 0 deletions packages/react-server-examples/code-splitting/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "react-server-examples-code-splitting",
"private": true,
"version": "0.0.1",
"description": "",
"main": "HelloWorld.js",
"scripts": {
"start": "IS_SERVER=true react-server start",
"clean": "rm -rf build __clientTemp",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "David Kuang",
"license": "Apache-2.0",
"dependencies": {
"q": "^2.0.3",
"react": "^15.4.1",
"react-dom": "15.4.1",
"react-server": "^0.6.3",
"react-server-cli": "^0.6.3"
},
"devDependencies": {
"babel-preset-react-server": "^0.6.0"
}
}
22 changes: 22 additions & 0 deletions packages/react-server-examples/code-splitting/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from "react"
import { RootElement } from "react-server";
import Body from "../components/Body";

// These are async functions that resolve to a single React component
import HeaderLoader from "../componentLoaders/HeaderLoader";
import FooterLoader from "../componentLoaders/FooterLoader";

export default class CodeSplittingPage {
getTitle() {
return "Code Splitting Demo";
}

getElements () {
return [
// childProps will be passed into the loaded component
<RootElement componentLoader={HeaderLoader} childProps={{ headerText: "Code Splitting Demo" }}/>,
<Body />,
<RootElement componentLoader={FooterLoader} />,
];
}
}
9 changes: 9 additions & 0 deletions packages/react-server-examples/code-splitting/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
routes: {
Index: {
path: ['/'],
method: 'get',
page: "./pages/index",
},
},
};
17 changes: 17 additions & 0 deletions packages/react-server-examples/code-splitting/webpackConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import webpack from "webpack";

export default (webpackConfig) => {

// Optional. This makes it so the child chunks receive a readable name versus
// just the chunk id
webpackConfig.output.chunkFilename = "[name]." + webpackConfig.output.chunkFilename;

// This is to prevent webpack from running the code in these blocks
// Needed because otherwise webpack will bundle in the async components
// into the main page bundle
webpackConfig.plugins.push(new webpack.DefinePlugin({
"process.env.IS_SERVER": 'false',
}));

return webpackConfig;
};
Loading

0 comments on commit 7f7f16f

Please sign in to comment.