Skip to content

Commit 2b4fb0d

Browse files
committed
Update Ch.20 SSR and corrections
1 parent 9155531 commit 2b4fb0d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+4621
-2036
lines changed

20/20.3/ssr-recipe/.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,3 @@
2121
npm-debug.log*
2222
yarn-debug.log*
2323
yarn-error.log*
24-
/dist

20/20.3/ssr-recipe/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ See the section about [deployment](https://facebook.github.io/create-react-app/d
3333

3434
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
3535

36-
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
36+
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
3737

3838
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
3939

20/20.3/ssr-recipe/config/env.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ dotenvFiles.forEach(dotenvFile => {
4646
// It works similar to `NODE_PATH` in Node itself:
4747
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
4848
// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
49-
// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
49+
// Otherwise, we risk importing Node.js core modules into an app instead of webpack shims.
5050
// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
5151
// We also resolve them to make sure all tools using them work consistently.
5252
const appDirectory = fs.realpathSync(process.cwd());
@@ -57,7 +57,7 @@ process.env.NODE_PATH = (process.env.NODE_PATH || '')
5757
.join(path.delimiter);
5858

5959
// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
60-
// injected into the application via DefinePlugin in Webpack configuration.
60+
// injected into the application via DefinePlugin in webpack configuration.
6161
const REACT_APP = /^REACT_APP_/i;
6262

6363
function getClientEnvironment(publicUrl) {
@@ -77,9 +77,17 @@ function getClientEnvironment(publicUrl) {
7777
// This should only be used as an escape hatch. Normally you would put
7878
// images into the `src` and `import` them in code to get their paths.
7979
PUBLIC_URL: publicUrl,
80+
// We support configuring the sockjs pathname during development.
81+
// These settings let a developer run multiple simultaneous projects.
82+
// They are used as the connection `hostname`, `pathname` and `port`
83+
// in webpackHotDevClient. They are used as the `sockHost`, `sockPath`
84+
// and `sockPort` options in webpack-dev-server.
85+
WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST,
86+
WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH,
87+
WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT,
8088
}
8189
);
82-
// Stringify all values so we can feed into Webpack DefinePlugin
90+
// Stringify all values so we can feed into webpack DefinePlugin
8391
const stringified = {
8492
'process.env': Object.keys(raw).reduce((env, key) => {
8593
env[key] = JSON.stringify(raw[key]);
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
'use strict';
2+
3+
const fs = require('fs');
4+
const path = require('path');
5+
const crypto = require('crypto');
6+
const chalk = require('react-dev-utils/chalk');
7+
const paths = require('./paths');
8+
9+
// Ensure the certificate and key provided are valid and if not
10+
// throw an easy to debug error
11+
function validateKeyAndCerts({ cert, key, keyFile, crtFile }) {
12+
let encrypted;
13+
try {
14+
// publicEncrypt will throw an error with an invalid cert
15+
encrypted = crypto.publicEncrypt(cert, Buffer.from('test'));
16+
} catch (err) {
17+
throw new Error(
18+
`The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`
19+
);
20+
}
21+
22+
try {
23+
// privateDecrypt will throw an error with an invalid key
24+
crypto.privateDecrypt(key, encrypted);
25+
} catch (err) {
26+
throw new Error(
27+
`The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${
28+
err.message
29+
}`
30+
);
31+
}
32+
}
33+
34+
// Read file and throw an error if it doesn't exist
35+
function readEnvFile(file, type) {
36+
if (!fs.existsSync(file)) {
37+
throw new Error(
38+
`You specified ${chalk.cyan(
39+
type
40+
)} in your env, but the file "${chalk.yellow(file)}" can't be found.`
41+
);
42+
}
43+
return fs.readFileSync(file);
44+
}
45+
46+
// Get the https config
47+
// Return cert files if provided in env, otherwise just true or false
48+
function getHttpsConfig() {
49+
const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env;
50+
const isHttps = HTTPS === 'true';
51+
52+
if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) {
53+
const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE);
54+
const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE);
55+
const config = {
56+
cert: readEnvFile(crtFile, 'SSL_CRT_FILE'),
57+
key: readEnvFile(keyFile, 'SSL_KEY_FILE'),
58+
};
59+
60+
validateKeyAndCerts({ ...config, keyFile, crtFile });
61+
return config;
62+
}
63+
return isHttps;
64+
}
65+
66+
module.exports = getHttpsConfig;

20/20.3/ssr-recipe/config/paths.js

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,25 @@
1+
2+
13
const path = require('path');
24
const fs = require('fs');
3-
const url = require('url');
5+
const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath');
46

57
// Make sure any symlinks in the project folder are resolved:
68
// https://github.com/facebook/create-react-app/issues/637
79
const appDirectory = fs.realpathSync(process.cwd());
810
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
911

10-
const envPublicUrl = process.env.PUBLIC_URL;
11-
12-
function ensureSlash(inputPath, needsSlash) {
13-
const hasSlash = inputPath.endsWith('/');
14-
if (hasSlash && !needsSlash) {
15-
return inputPath.substr(0, inputPath.length - 1);
16-
} else if (!hasSlash && needsSlash) {
17-
return `${inputPath}/`;
18-
} else {
19-
return inputPath;
20-
}
21-
}
22-
23-
const getPublicUrl = appPackageJson =>
24-
envPublicUrl || require(appPackageJson).homepage;
25-
2612
// We use `PUBLIC_URL` environment variable or "homepage" field to infer
2713
// "public path" at which the app is served.
28-
// Webpack needs to know it to put the right <script> hrefs into HTML even in
14+
// webpack needs to know it to put the right <script> hrefs into HTML even in
2915
// single-page apps that may serve index.html for nested URLs like /todos/42.
3016
// We can't use a relative path in HTML because we don't want to load something
3117
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
32-
function getServedPath(appPackageJson) {
33-
const publicUrl = getPublicUrl(appPackageJson);
34-
const servedUrl =
35-
envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/');
36-
return ensureSlash(servedUrl, true);
37-
}
18+
const publicUrlOrPath = getPublicUrlOrPath(
19+
process.env.NODE_ENV === 'development',
20+
require(resolveApp('package.json')).homepage,
21+
process.env.PUBLIC_URL
22+
);
3823

3924
const moduleFileExtensions = [
4025
'web.mjs',
@@ -47,7 +32,7 @@ const moduleFileExtensions = [
4732
'tsx',
4833
'json',
4934
'web.jsx',
50-
'jsx'
35+
'jsx',
5136
];
5237

5338
// Resolve file paths in the same order as webpack
@@ -79,10 +64,11 @@ module.exports = {
7964
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
8065
proxySetup: resolveApp('src/setupProxy.js'),
8166
appNodeModules: resolveApp('node_modules'),
82-
publicUrl: getPublicUrl(resolveApp('package.json')),
83-
servedPath: getServedPath(resolveApp('package.json')),
8467
ssrIndexJs: resolveApp('src/index.server.js'),
85-
ssrBuild: resolveApp('dist')
68+
ssrBuild: resolveApp('dist'),
69+
publicUrlOrPath,
8670
};
8771

72+
73+
8874
module.exports.moduleFileExtensions = moduleFileExtensions;

20/20.3/ssr-recipe/config/webpack.config.js

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
3535
// makes for a smoother build process.
3636
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
3737

38+
const isExtendingEslintConfig = process.env.EXTEND_ESLINT === 'true';
39+
3840
const imageInlineSizeLimit = parseInt(
3941
process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
4042
);
@@ -59,32 +61,23 @@ module.exports = function(webpackEnv) {
5961
const isEnvProductionProfile =
6062
isEnvProduction && process.argv.includes('--profile');
6163

62-
// Webpack uses `publicPath` to determine where the app is being served from.
63-
// It requires a trailing slash, or the file assets will get an incorrect path.
64-
// In development, we always serve from the root. This makes config easier.
65-
const publicPath = isEnvProduction
66-
? paths.servedPath
67-
: isEnvDevelopment && '/';
68-
// Some apps do not use client-side routing with pushState.
69-
// For these, "homepage" can be set to "." to enable relative asset paths.
70-
const shouldUseRelativeAssetPaths = publicPath === './';
71-
72-
// `publicUrl` is just like `publicPath`, but we will provide it to our app
64+
// We will provide `paths.publicUrlOrPath` to our app
7365
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
7466
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
75-
const publicUrl = isEnvProduction
76-
? publicPath.slice(0, -1)
77-
: isEnvDevelopment && '';
7867
// Get environment variables to inject into our app.
79-
const env = getClientEnvironment(publicUrl);
68+
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
8069

8170
// common function to get style loaders
8271
const getStyleLoaders = (cssOptions, preProcessor) => {
8372
const loaders = [
8473
isEnvDevelopment && require.resolve('style-loader'),
8574
isEnvProduction && {
8675
loader: MiniCssExtractPlugin.loader,
87-
options: shouldUseRelativeAssetPaths ? { publicPath: '../../' } : {},
76+
// css is located in `static/css`, use '../../' to locate index.html folder
77+
// in production `paths.publicUrlOrPath` can be a relative path
78+
options: paths.publicUrlOrPath.startsWith('.')
79+
? { publicPath: '../../' }
80+
: {},
8881
},
8982
{
9083
loader: require.resolve('css-loader'),
@@ -181,9 +174,10 @@ module.exports = function(webpackEnv) {
181174
chunkFilename: isEnvProduction
182175
? 'static/js/[name].[contenthash:8].chunk.js'
183176
: isEnvDevelopment && 'static/js/[name].chunk.js',
177+
// webpack uses `publicPath` to determine where the app is being served from.
178+
// It requires a trailing slash, or the file assets will get an incorrect path.
184179
// We inferred the "public path" (such as / or /my-project) from homepage.
185-
// We use "/" in development.
186-
publicPath: publicPath,
180+
publicPath: paths.publicUrlOrPath,
187181
// Point sourcemap entries to original disk location (format as URL on Windows)
188182
devtoolModuleFilenameTemplate: isEnvProduction
189183
? info =>
@@ -192,7 +186,7 @@ module.exports = function(webpackEnv) {
192186
.replace(/\\/g, '/')
193187
: isEnvDevelopment &&
194188
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
195-
// Prevents conflicts when multiple Webpack runtimes (from different apps)
189+
// Prevents conflicts when multiple webpack runtimes (from different apps)
196190
// are used on the same page.
197191
jsonpFunction: `webpackJsonp${appPackageJson.name}`,
198192
// this defaults to 'window', but by setting it to 'this' then
@@ -259,8 +253,8 @@ module.exports = function(webpackEnv) {
259253
: false,
260254
},
261255
cssProcessorPluginOptions: {
262-
preset: ['default', { minifyFontValues: { removeQuotes: false } }]
263-
}
256+
preset: ['default', { minifyFontValues: { removeQuotes: false } }],
257+
},
264258
}),
265259
],
266260
// Automatically split vendor and commons
@@ -278,7 +272,7 @@ module.exports = function(webpackEnv) {
278272
},
279273
},
280274
resolve: {
281-
// This allows you to set a fallback for where Webpack should look for modules.
275+
// This allows you to set a fallback for where webpack should look for modules.
282276
// We placed these paths second because we want `node_modules` to "win"
283277
// if there are any conflicts. This matches Node resolution mechanism.
284278
// https://github.com/facebook/create-react-app/issues/253
@@ -319,7 +313,7 @@ module.exports = function(webpackEnv) {
319313
},
320314
resolveLoader: {
321315
plugins: [
322-
// Also related to Plug'n'Play, but this time it tells Webpack to load its loaders
316+
// Also related to Plug'n'Play, but this time it tells webpack to load its loaders
323317
// from the current package.
324318
PnpWebpackPlugin.moduleLoader(module),
325319
],
@@ -549,9 +543,8 @@ module.exports = function(webpackEnv) {
549543
// Makes some environment variables available in index.html.
550544
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
551545
// <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
552-
// In production, it will be an empty string unless you specify "homepage"
546+
// It will be an empty string unless you specify "homepage"
553547
// in `package.json`, in which case it will be the pathname of that URL.
554-
// In development, this will be an empty string.
555548
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
556549
// This gives some necessary context to module not found errors, such as
557550
// the requesting resource.
@@ -569,7 +562,7 @@ module.exports = function(webpackEnv) {
569562
// See https://github.com/facebook/create-react-app/issues/240
570563
isEnvDevelopment && new CaseSensitivePathsPlugin(),
571564
// If you require a missing module and then `npm install` it, you still have
572-
// to restart the development server for Webpack to discover it. This plugin
565+
// to restart the development server for webpack to discover it. This plugin
573566
// makes the discovery automatic so you don't have to restart.
574567
// See https://github.com/facebook/create-react-app/issues/186
575568
isEnvDevelopment &&
@@ -589,7 +582,7 @@ module.exports = function(webpackEnv) {
589582
// can be used to reconstruct the HTML if necessary
590583
new ManifestPlugin({
591584
fileName: 'asset-manifest.json',
592-
publicPath: publicPath,
585+
publicPath: paths.publicUrlOrPath,
593586
generate: (seed, files, entrypoints) => {
594587
const manifestFiles = files.reduce((manifest, file) => {
595588
manifest[file.name] = file.path;
@@ -606,19 +599,19 @@ module.exports = function(webpackEnv) {
606599
},
607600
}),
608601
// Moment.js is an extremely popular library that bundles large locale files
609-
// by default due to how Webpack interprets its code. This is a practical
602+
// by default due to how webpack interprets its code. This is a practical
610603
// solution that requires the user to opt into importing specific locales.
611604
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
612605
// You can remove this if you don't use Moment.js:
613606
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
614607
// Generate a service worker script that will precache, and keep up to date,
615-
// the HTML & assets that are part of the Webpack build.
608+
// the HTML & assets that are part of the webpack build.
616609
isEnvProduction &&
617610
new WorkboxWebpackPlugin.GenerateSW({
618611
clientsClaim: true,
619612
exclude: [/\.map$/, /asset-manifest\.json$/],
620613
importWorkboxFrom: 'cdn',
621-
navigateFallback: publicUrl + '/index.html',
614+
navigateFallback: paths.publicUrlOrPath + 'index.html',
622615
navigateFallbackBlacklist: [
623616
// Exclude URLs starting with /_, as they're likely an API call
624617
new RegExp('^/_'),
@@ -658,7 +651,7 @@ module.exports = function(webpackEnv) {
658651
}),
659652
].filter(Boolean),
660653
// Some libraries import Node modules but don't use them in the browser.
661-
// Tell Webpack to provide empty mocks for them so importing them works.
654+
// Tell webpack to provide empty mocks for them so importing them works.
662655
node: {
663656
module: 'empty',
664657
dgram: 'empty',

20/20.3/ssr-recipe/config/webpack.config.server.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ const cssModuleRegex = /\.module\.css$/;
99
const sassRegex = /\.(scss|sass)$/;
1010
const sassModuleRegex = /\.module\.(scss|sass)$/;
1111

12-
const publicUrl = paths.servedPath.slice(0, -1);
13-
const env = getClientEnvironment(publicUrl);
12+
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
1413

1514
module.exports = {
1615
mode: 'production',
@@ -20,7 +19,7 @@ module.exports = {
2019
path: paths.ssrBuild,
2120
filename: 'server.js',
2221
chunkFilename: 'js/[name].chunk.js',
23-
publicPath: paths.servedPath
22+
publicPath: paths.publicUrlOrPath
2423
},
2524
module: {
2625
rules: [

0 commit comments

Comments
 (0)