Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add code splitting support to RootElement #963

Merged
merged 3 commits into from
Oct 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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