From 7f7f16f089717981640c4ddda90ac84691f3426e Mon Sep 17 00:00:00 2001
From: David
Date: Tue, 17 Oct 2017 17:09:55 -0700
Subject: [PATCH] Add code splitting support to RootElement (#963)
* Added support for componentLoader on RootElement
* Added code splitting example
* Fixed isse where client transitions with reuseDom enabled was broken
---
packages/react-server-examples/README.md | 1 +
.../code-splitting/.babelrc | 3 +
.../code-splitting/.gitignore | 2 +
.../code-splitting/.reactserverrc | 4 +
.../code-splitting/README.md | 64 +
.../componentLoaders/FooterLoader.js | 27 +
.../componentLoaders/HeaderLoader.js | 27 +
.../code-splitting/components/Body.js | 30 +
.../code-splitting/components/Footer.js | 30 +
.../code-splitting/components/Header.js | 14 +
.../code-splitting/package.json | 24 +
.../code-splitting/pages/index.js | 22 +
.../code-splitting/routes.js | 9 +
.../code-splitting/webpackConfig.js | 17 +
.../code-splitting/yarn.lock | 4078 +++++++++++++++++
.../core/components/RootElement.js | 53 +-
16 files changed, 4393 insertions(+), 12 deletions(-)
create mode 100644 packages/react-server-examples/code-splitting/.babelrc
create mode 100644 packages/react-server-examples/code-splitting/.gitignore
create mode 100644 packages/react-server-examples/code-splitting/.reactserverrc
create mode 100644 packages/react-server-examples/code-splitting/README.md
create mode 100644 packages/react-server-examples/code-splitting/componentLoaders/FooterLoader.js
create mode 100644 packages/react-server-examples/code-splitting/componentLoaders/HeaderLoader.js
create mode 100644 packages/react-server-examples/code-splitting/components/Body.js
create mode 100644 packages/react-server-examples/code-splitting/components/Footer.js
create mode 100644 packages/react-server-examples/code-splitting/components/Header.js
create mode 100644 packages/react-server-examples/code-splitting/package.json
create mode 100644 packages/react-server-examples/code-splitting/pages/index.js
create mode 100644 packages/react-server-examples/code-splitting/routes.js
create mode 100644 packages/react-server-examples/code-splitting/webpackConfig.js
create mode 100644 packages/react-server-examples/code-splitting/yarn.lock
diff --git a/packages/react-server-examples/README.md b/packages/react-server-examples/README.md
index 22b2b7cd5..47ff05f6d 100644
--- a/packages/react-server-examples/README.md
+++ b/packages/react-server-examples/README.md
@@ -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.
diff --git a/packages/react-server-examples/code-splitting/.babelrc b/packages/react-server-examples/code-splitting/.babelrc
new file mode 100644
index 000000000..908903e65
--- /dev/null
+++ b/packages/react-server-examples/code-splitting/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["react-server"],
+}
diff --git a/packages/react-server-examples/code-splitting/.gitignore b/packages/react-server-examples/code-splitting/.gitignore
new file mode 100644
index 000000000..250f94ca2
--- /dev/null
+++ b/packages/react-server-examples/code-splitting/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+__clientTemp
diff --git a/packages/react-server-examples/code-splitting/.reactserverrc b/packages/react-server-examples/code-splitting/.reactserverrc
new file mode 100644
index 000000000..9ef2320cf
--- /dev/null
+++ b/packages/react-server-examples/code-splitting/.reactserverrc
@@ -0,0 +1,4 @@
+{
+ "routesFile": "./routes.js",
+ "webpackConfig": "./webpackConfig.js"
+}
diff --git a/packages/react-server-examples/code-splitting/README.md b/packages/react-server-examples/code-splitting/README.md
new file mode 100644
index 000000000..777a9e063
--- /dev/null
+++ b/packages/react-server-examples/code-splitting/README.md
@@ -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 (
+
+ );
+}
+```
+
+This will render the component that calling `loader` resolves to and with `displayText` passed in as a prop to it.
+```jsx
+getElements() {
+ return (
+
+ );
+}
+```
+
+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 (
+
+ );
+}
+```
+
+This will render the resolved component with `Content` as a child of the resolved component.
+```jsx
+getElements() {
+ return (
+
+
+
+ );
+}
+```
diff --git a/packages/react-server-examples/code-splitting/componentLoaders/FooterLoader.js b/packages/react-server-examples/code-splitting/componentLoaders/FooterLoader.js
new file mode 100644
index 000000000..0d406b503
--- /dev/null
+++ b/packages/react-server-examples/code-splitting/componentLoaders/FooterLoader.js
@@ -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;
diff --git a/packages/react-server-examples/code-splitting/componentLoaders/HeaderLoader.js b/packages/react-server-examples/code-splitting/componentLoaders/HeaderLoader.js
new file mode 100644
index 000000000..c8ebc9746
--- /dev/null
+++ b/packages/react-server-examples/code-splitting/componentLoaders/HeaderLoader.js
@@ -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;
diff --git a/packages/react-server-examples/code-splitting/components/Body.js b/packages/react-server-examples/code-splitting/components/Body.js
new file mode 100644
index 000000000..be623476c
--- /dev/null
+++ b/packages/react-server-examples/code-splitting/components/Body.js
@@ -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 (
+