Skip to content

Commit 452b6aa

Browse files
committed
Add templates, add serve script, improve other scripts
1 parent 8ff41ae commit 452b6aa

File tree

11 files changed

+187
-16931
lines changed

11 files changed

+187
-16931
lines changed

bin/react-scripts.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ switch (script) {
2828
case 'build':
2929
case 'eject':
3030
case 'start':
31-
case 'test': {
31+
case 'test':
32+
case 'serve': {
3233
const result = spawn.sync(
3334
'node',
3435
nodeArgs

config/webpack.ssr.config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ module.exports = function (webpackEnv) {
4141
// Avoid shadowing of process.env for ssr handler
4242
webpack.DefinePlugin
4343
].every(pluginClass => !(plugin instanceof pluginClass))
44-
)
44+
),
45+
// do not bundle express
46+
externals: [
47+
"express"
48+
]
4549
},
4650
// client compiler config
4751
{

package-lock.json

Lines changed: 0 additions & 16922 deletions
This file was deleted.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"dependencies": {
3535
"@babel/core": "7.1.6",
3636
"@svgr/webpack": "2.4.1",
37+
"@types/express": "^4.16.1",
3738
"babel-core": "7.0.0-bridge.0",
3839
"babel-eslint": "9.0.0",
3940
"babel-jest": "23.6.0",
@@ -53,6 +54,7 @@
5354
"eslint-plugin-import": "2.14.0",
5455
"eslint-plugin-jsx-a11y": "6.1.2",
5556
"eslint-plugin-react": "7.11.1",
57+
"express": "^4.16.4",
5658
"file-loader": "2.0.0",
5759
"fork-ts-checker-webpack-plugin-alt": "0.4.14",
5860
"fs-extra": "7.0.0",

scripts/init.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,11 @@ module.exports = function(
9595

9696
// Setup the script rules
9797
appPackage.scripts = {
98-
start: 'react-scripts start',
99-
build: 'react-scripts build',
100-
test: 'react-scripts test',
101-
eject: 'react-scripts eject',
98+
start: 'react-scripts-with-ssr start',
99+
build: 'react-scripts-with-ssr build',
100+
test: 'react-scripts-with-ssr test',
101+
eject: 'react-scripts-with-ssr eject',
102+
serve: 'react-scripts-with-ssr serve',
102103
};
103104

104105
// Setup the eslint config

scripts/serve.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const express = require('express');
2+
const path = require('path');
3+
const paths = require('../config/paths');
4+
5+
// read environment variables
6+
process.env.BABEL_ENV = 'production';
7+
process.env.NODE_ENV = 'production';
8+
require('../config/env');
9+
10+
const PORT = parseInt(process.env.PORT) || 3000;
11+
const PUBLIC_PATH = process.env.PUBLIC_PATH || '/';
12+
13+
// serve static files from build dir
14+
process.chdir(paths.appBuild);
15+
16+
// include compiled server-side request handler
17+
const ssrModuleFile = path.resolve(paths.appBuild, "ssr.js");
18+
const ssrModule = require(ssrModuleFile);
19+
20+
// start express server
21+
console.log(`Serving from '${process.cwd()}' with public path '${PUBLIC_PATH}'`);
22+
const app = express();
23+
app.use(PUBLIC_PATH, ssrModule.default);
24+
app.listen(PORT, () => console.log(`Open http://localhost:${PORT}${PUBLIC_PATH} for testing!`));

scripts/utils/verifyTypeScriptSetup.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ function verifyTypeScriptSetup() {
252252
if (!fs.existsSync(paths.appTypeDeclarations)) {
253253
fs.writeFileSync(
254254
paths.appTypeDeclarations,
255-
`/// <reference types="react-scripts" />${os.EOL}`
255+
`/// <reference types="react-scripts-with-ssr" />${os.EOL}`
256256
);
257257
}
258258
}

template-typescript/public/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
</head>
2727
<body>
2828
<noscript>You need to enable JavaScript to run this app.</noscript>
29-
<div id="root"></div>
29+
<div id="root">ROOT</div>
3030
<!--
3131
This HTML file is a template.
3232
If you open it directly in the browser, you will see an empty page.

template-typescript/src/index.ssr.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* This file is supposed to export a request handler, which implements server-side rendering in a Node.js runtime
3+
* environment.
4+
*
5+
* In production, the request handler may be imported by an executable node script, which sets up an express server.
6+
* A sample script is provided in `react-scripts-with-ssr/scripts/serve.js`, which you may start with `npm run serve`
7+
* to test your production builds of the request handler. That script however is not part of the compilation. It is up
8+
* to you to implement, how the request handler is integrated in your server-side infrastructure.
9+
*
10+
* This file may also export a function called `devServerHandler`, which is supposed to return a request handler for
11+
* the development environment. That request handler is plugged into the local webpack dev server started by `npm start`.
12+
*/
13+
import React from 'react';
14+
import { renderToNodeStream } from "react-dom/server";
15+
import * as express from "express";
16+
import * as fs from "fs";
17+
import * as path from "path";
18+
19+
import App from './App';
20+
21+
// provide a way to emulate fs access in development mode when builds are served from in-memory
22+
let readFileSync: (filename: string) => Buffer = fs.readFileSync;
23+
24+
const router = express.Router();
25+
// serve static files from the node runtime's current working dir
26+
router.use(express.static(
27+
// serve files from current working dir
28+
".",
29+
{
30+
// do not send index.html for "/"
31+
index: false
32+
}
33+
));
34+
35+
// do server-side rendering
36+
router.use((request: express.Request, response: express.Response, next: express.NextFunction) => {
37+
const template = readFileSync("index.html").toString();
38+
const [head, tail] = template.split("ROOT");
39+
const stream = renderToNodeStream(<App />);
40+
response.write(head);
41+
stream.pipe(response, { end: false });
42+
stream.on("end", () => {
43+
response.write(tail);
44+
response.end();
45+
});
46+
});
47+
48+
/**
49+
* Export a request handler. This can be plugged into an express instance or deployed as a serverless function.
50+
* An express router itself implements the request handler interface (composite design pattern).
51+
*/
52+
export default router;
53+
54+
/**
55+
* Supposed to return a request handler which can be plugged into a webpack dev server during development.
56+
* This function should return a somehow altered or decorated version of the 'export default' request handler,
57+
* which handles differences between development and production environment.
58+
* @param compiler webpack compiler which compiles the ssr entry point
59+
*/
60+
export const devServerHandler = (compiler: any) => {
61+
// redirect file access to use in-memory fs of the compiler
62+
readFileSync = fileName =>
63+
compiler.outputFileSystem.readFileSync(path.resolve("dist", fileName));
64+
65+
// wrap the production router to handle some requests in another way
66+
const devServerRouter = express.Router();
67+
68+
// skip all requests to static files, the webpack dev middleware will handle them
69+
const notAStaticFile = /^\/(?!static|favicon\.ico).*/;
70+
devServerRouter.use(notAStaticFile, router);
71+
72+
return devServerRouter;
73+
};

template/public/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
</head>
2727
<body>
2828
<noscript>You need to enable JavaScript to run this app.</noscript>
29-
<div id="root"></div>
29+
<div id="root">ROOT</div>
3030
<!--
3131
This HTML file is a template.
3232
If you open it directly in the browser, you will see an empty page.

template/src/index.ssr.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* This file is supposed to export a request handler, which implements server-side rendering in a Node.js runtime
3+
* environment.
4+
*
5+
* In production, the request handler may be imported by an executable node script, which sets up an express server.
6+
* A sample script is provided in `react-scripts-with-ssr/scripts/serve.js`, which you may start with `npm run serve`
7+
* to test your production builds of the request handler. That script however is not part of the compilation. It is up
8+
* to you to implement, how the request handler is integrated in your server-side infrastructure.
9+
*
10+
* This file may also export a function called `devServerHandler`, which is supposed to return a request handler for
11+
* the development environment. That request handler is plugged into the local webpack dev server started by `npm start`.
12+
*/
13+
import React from 'react';
14+
import { renderToNodeStream } from "react-dom/server";
15+
import * as express from "express";
16+
import * as fs from "fs";
17+
import * as path from "path";
18+
19+
import App from './App';
20+
21+
// provide a way to emulate fs access in development mode when builds are served from in-memory
22+
let readFileSync = fs.readFileSync;
23+
24+
const router = express.Router();
25+
// serve static files from the node runtime's current working dir
26+
router.use(express.static(
27+
// serve files from current working dir
28+
".",
29+
{
30+
// do not send index.html for "/"
31+
index: false
32+
}
33+
));
34+
35+
// do server-side rendering
36+
router.use((request, response, next) => {
37+
const template = readFileSync("index.html").toString();
38+
const [head, tail] = template.split("ROOT");
39+
const stream = renderToNodeStream(<App />);
40+
response.write(head);
41+
stream.pipe(response, { end: false });
42+
stream.on("end", () => {
43+
response.write(tail);
44+
response.end();
45+
});
46+
});
47+
48+
/**
49+
* Export a request handler. This can be plugged into an express instance or deployed as a serverless function.
50+
* An express router itself implements the request handler interface (composite design pattern).
51+
*/
52+
export default router;
53+
54+
/**
55+
* Supposed to return a request handler which can be plugged into a webpack dev server during development.
56+
* This function should return a somehow altered or decorated version of the 'export default' request handler,
57+
* which handles differences between development and production environment.
58+
* @param compiler webpack compiler which compiles the ssr entry point
59+
*/
60+
export const devServerHandler = compiler => {
61+
// redirect file access to use in-memory fs of the compiler
62+
readFileSync = fileName =>
63+
compiler.outputFileSystem.readFileSync(path.resolve("dist", fileName));
64+
65+
// wrap the production router to handle some requests in another way
66+
const devServerRouter = express.Router();
67+
68+
// skip all requests to static files, the webpack dev middleware will handle them
69+
const notAStaticFile = /^\/(?!static|favicon\.ico).*/;
70+
devServerRouter.use(notAStaticFile, router);
71+
72+
return devServerRouter;
73+
};

0 commit comments

Comments
 (0)