Skip to content

Commit

Permalink
fix: enable streaming ssr (#227)
Browse files Browse the repository at this point in the history
* fix: enable streaming ssr

* feat: enable toggle streaming

* fix: add preset-typescript

* docs: update contributing.md

* chore: bump 5.0.2
  • Loading branch information
Antony Budianto authored May 7, 2022
1 parent 4dda869 commit 2710b10
Show file tree
Hide file tree
Showing 17 changed files with 130 additions and 71 deletions.
12 changes: 7 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,19 @@ Since we need a way to test the cra-universal locally and seamlessly, we have a

## Running CRA Universal locally

Use `temp:start:client` to run client and server locally.
- Run `npm run temp:start:client` to run client locally.
- Run `npm run temp:start:server` to run server locally.

- Normal CRA starts at http://localhost:3000
- Server-rendered CRA Universal starts at http://localhost:3001
- Server-rendered CRA Universal starts at http://localhost:3001 (you'll mostly use this for development)

To run a production build of this app, preserving local CRA Universal, use these commands:

```sh
yarn temp:build
cd packages/cra-universal/templates
node --preserve-symlinks dist/server/bundle.js
npm run temp:build
cd packages/cra-universal/templates/dist
pnpm i
npm run serve
```

### Webpack versions mismatch
Expand Down
6 changes: 3 additions & 3 deletions packages/@cra-express/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cra-express/core",
"version": "5.0.1",
"version": "5.0.2",
"description": "Core module for cra-universal server",
"main": "lib/index.js",
"files": [
Expand All @@ -20,8 +20,8 @@
"express": "^4.18.1"
},
"dependencies": {
"@cra-express/static-loader": "^5",
"@cra-express/universal-loader": "^5"
"@cra-express/static-loader": "^5.0.2",
"@cra-express/universal-loader": "^5.0.2"
},
"author": "Antony Budianto <[email protected]>",
"license": "MIT"
Expand Down
2 changes: 1 addition & 1 deletion packages/@cra-express/redux-prefetcher/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cra-express/redux-prefetcher",
"version": "5.0.1",
"version": "5.0.2",
"description": "Redux Prefetcher for prefetching on server",
"main": "lib/index.js",
"files": [
Expand Down
2 changes: 1 addition & 1 deletion packages/@cra-express/router-prefetcher/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cra-express/router-prefetcher",
"version": "5.0.1",
"version": "5.0.2",
"description": "Router Prefetcher for prefetching on server",
"main": "lib/index.js",
"files": [
Expand Down
2 changes: 1 addition & 1 deletion packages/@cra-express/static-loader/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cra-express/static-loader",
"version": "5.0.1",
"version": "5.0.2",
"description": "Static loader",
"main": "lib/index.js",
"files": [
Expand Down
2 changes: 1 addition & 1 deletion packages/@cra-express/universal-loader/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cra-express/universal-loader",
"version": "5.0.1",
"version": "5.0.2",
"description": "Universal loader",
"main": "lib/index.js",
"files": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { renderToPipeableStream } from 'react-dom/server';

/**
* Reference:
* https://reactjs.org/docs/react-dom-server.html#rendertopipeablestream
* @param {Request} req
* @param {Response} res
* @param {JSX.Element} reactEl
* @param {string} htmlData
* @param {any} options
*/

export default function pipeStreamRenderer(
req,
res,
Expand All @@ -9,49 +19,54 @@ export default function pipeStreamRenderer(
) {
let str;
let error;
const segments = htmlData.split(`<div id="root">`);
const streaming = reactEl.props.streaming || false;

try {
const { pipe } = renderToPipeableStream(reactEl, {
onAllReady() {
/**
* Allows full customization
*/
if (typeof options.onAllReady === 'function') {
return options.onAllReady({ req, res, htmlData, error, pipe });
}

res.statusCode = error ? 500 : 200;
res.setHeader('Content-type', 'text/html');
const processStream = (res, stream) => {
res.statusCode = error ? 500 : 200;
res.setHeader('Content-type', 'text/html');

const segments = htmlData.split(`<div id="root">`);
const errorScript = error
? '<script type="text/javascript">window.__ssrError=true;</script>'
: '';
const errorScript = error
? '<script type="text/javascript">window.__ssrError=true;</script>'
: '';

/**
* Return fallback when error
*/
if (error) {
res.send(
`${segments[0]}${errorScript}\n<div id="root">${segments[1]}`
);
return;
}
res.write(segments[0] + errorScript + '<div id="root">');

res.write(segments[0] + errorScript + '<div id="root">');
stream.pipe(res);
};

pipe(res);
try {
const stream = renderToPipeableStream(reactEl, {
onShellReady() {
if (typeof options.onShellReady === 'function') {
return options.onShellReady({ req, res, htmlData, error, stream });
}

if (typeof options.onEndReplace === 'function') {
segments[1] = options.onEndReplace(segments[1]);
if (streaming) {
processStream(res, stream);
}
},
onAllReady() {
if (typeof options.onAllReady === 'function') {
return options.onAllReady({ req, res, htmlData, error, stream });
}

if (!streaming) {
processStream(res, stream);
}
res.write(segments[1]);
res.end();
},
onError(e) {
error = true;
console.error('crau/ssr-on-error', e.message);
},
onShellError(x) {
console.error('crau/ssr-shell-error: ', x.message);
/**
* Return fallback when error
*/
error = true;
console.error('crau/ssr-on-shell-error: ', x.message);
res.send(`${segments[0]}${errorScript}\n<div id="root">${segments[1]}`);
},
});
} catch (e) {
Expand Down
1 change: 1 addition & 0 deletions packages/babel-preset-cra-universal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var preset = {
presets: [
[require.resolve('@babel/preset-env'), { modules: false }],
[require.resolve('@babel/preset-react'), { runtime: 'automatic' }],
require.resolve('@babel/preset-typescript'),
],
plugins: [
// class { handleThing = () => { } }
Expand Down
3 changes: 2 additions & 1 deletion packages/babel-preset-cra-universal/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "babel-preset-cra-universal",
"version": "5.0.1",
"version": "5.0.2",
"main": "index.js",
"description": "Babel preset for cra-universal",
"repository": {
Expand All @@ -20,6 +20,7 @@
"@babel/plugin-transform-react-jsx-source": "7.16.7",
"@babel/preset-env": "7.17.10",
"@babel/preset-react": "7.16.7",
"@babel/preset-typescript": "7.16.7",
"babel-plugin-dynamic-import-node": "2.3.0",
"babel-plugin-transform-react-remove-prop-types": "0.4.24"
},
Expand Down
6 changes: 3 additions & 3 deletions packages/cra-universal/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cra-universal",
"version": "5.0.1",
"version": "5.0.2",
"description": "Create React App Universal CLI",
"main": "src/index.js",
"files": [
Expand Down Expand Up @@ -30,10 +30,10 @@
"dependencies": {
"@babel/core": "7.17.10",
"@babel/register": "7.17.7",
"@cra-express/core": "^5.0.1",
"@cra-express/core": "^5.0.2",
"@svgr/webpack": "6.2.1",
"babel-loader": "^8.2.5",
"babel-preset-cra-universal": "^5.0.1",
"babel-preset-cra-universal": "^5.0.2",
"chalk": "^2.4.2",
"css-loader": "^6.7.1",
"del": "^5.1.0",
Expand Down
13 changes: 7 additions & 6 deletions packages/cra-universal/templates/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "cra-template",
"version": "5.0.1",
"version": "5.0.2",
"private": true,
"devDependencies": {
"better-npm-run": "^0.1.1",
"cra-universal": "^5.0.1"
"cra-universal": "^5.0.2"
},
"dependencies": {
"@cra-express/core": "^5.0.1",
"@cra-express/core": "^5.0.2",
"@types/webpack-env": "1.15.1",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.1.1",
Expand All @@ -16,12 +16,13 @@
"@types/node": "^16.11.32",
"@types/react": "^18.0.8",
"@types/react-dom": "^18.0.3",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react": "18.2.0-next-e531a4a62-20220505",
"react-dom": "18.2.0-next-e531a4a62-20220505",
"react-scripts": "5.0.1",
"typescript": "^4.6.4",
"web-vitals": "^2.1.4",
"express": "4.18.1"
"express": "4.18.1",
"react-error-boundary": "3.1.4"
},
"scripts": {
"crau:start": "cra-universal start",
Expand Down
6 changes: 3 additions & 3 deletions packages/cra-universal/templates/server/app.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
const path = require('path');
const React = require('react');
import path from 'path';
import React from 'react';
import { createReactAppExpress } from '@cra-express/core';

let App = require('../src/App').default;
const clientBuildPath = path.resolve(__dirname, '../client');

const app = createReactAppExpress({
clientBuildPath,
universalRender: (req, res) => <App />
universalRender: (req, res) => <App />,
});

if (module.hot) {
Expand Down
43 changes: 28 additions & 15 deletions packages/cra-universal/templates/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
import { Component } from 'react';
import { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';

import './App.css';
import { ReactComponent as ReactSVG } from './react.svg';
import LazyContent1 from './Content/lazy1';
import LazyContent2 from './Content/lazy2';

class App extends Component {
render() {
return (
<div className="App">
<div className="App-header">
<h2>Welcome to React</h2>
</div>
<ReactSVG width={200} height={200} />
<p className="App-intro">
To get started, edit <code>src/App.tsx</code> and save to reload.
</p>
const ErrorFallback = () => <div>Error</div>;

const App = () => {
return (
<div className="App">
<div className="App-header">
<h2>Welcome to React</h2>
</div>
);
}
}
<ReactSVG width={200} height={200} />
<p className="App-intro">
To get started, edit <code>src/App.tsx</code> and save to reload.
</p>
<LazyContent2 />
<Suspense fallback={<div>Loading, please wait...</div>}>
<ErrorBoundary FallbackComponent={ErrorFallback}>
<LazyContent1 />
</ErrorBoundary>
</Suspense>
</div>
);
};

App.defaultProps = {
streaming: true,
};

export default App;
5 changes: 5 additions & 0 deletions packages/cra-universal/templates/src/Content/Content1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const Content1 = () => {
return <div>LazyContent1 - with Suspense</div>;
};

export default Content1;
5 changes: 5 additions & 0 deletions packages/cra-universal/templates/src/Content/Content2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const Content2 = () => {
return <div>LazyContent2 - no Suspense</div>;
};

export default Content2;
8 changes: 8 additions & 0 deletions packages/cra-universal/templates/src/Content/lazy1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { lazy } from 'react';

/**
* Demonstrate lazy with Suspense
*/
const LazyContent1 = lazy(() => import('./Content1'));

export default LazyContent1;
8 changes: 8 additions & 0 deletions packages/cra-universal/templates/src/Content/lazy2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { lazy } from 'react';

/**
* Demonstrate "always" SSR component
*/
const LazyContent2 = lazy(() => import('./Content2'));

export default LazyContent2;

0 comments on commit 2710b10

Please sign in to comment.