diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js
index 1208d7fc776..260f73ceea0 100644
--- a/packages/react-dev-utils/WebpackDevServerUtils.js
+++ b/packages/react-dev-utils/WebpackDevServerUtils.js
@@ -34,20 +34,20 @@ if (isSmokeTest) {
};
}
-function prepareUrls(protocol, host, port) {
+function prepareUrls(protocol, host, port, pathname) {
const formatUrl = hostname =>
url.format({
protocol,
hostname,
port,
- pathname: '/',
+ pathname,
});
const prettyPrintUrl = hostname =>
url.format({
protocol,
hostname,
port: chalk.bold(port),
- pathname: '/',
+ pathname,
});
const isUnspecifiedHost = host === '0.0.0.0' || host === '::';
diff --git a/packages/react-dev-utils/errorOverlayMiddleware.js b/packages/react-dev-utils/errorOverlayMiddleware.js
index 873b1994732..57a6003e45d 100644
--- a/packages/react-dev-utils/errorOverlayMiddleware.js
+++ b/packages/react-dev-utils/errorOverlayMiddleware.js
@@ -9,9 +9,9 @@
const launchEditor = require('./launchEditor');
const launchEditorEndpoint = require('./launchEditorEndpoint');
-module.exports = function createLaunchEditorMiddleware() {
+module.exports = function createLaunchEditorMiddleware(servedPathPathname) {
return function launchEditorMiddleware(req, res, next) {
- if (req.url.startsWith(launchEditorEndpoint)) {
+ if (req.url.startsWith(`${servedPathPathname}${launchEditorEndpoint}`)) {
const lineNumber = parseInt(req.query.lineNumber, 10) || 1;
const colNumber = parseInt(req.query.colNumber, 10) || 1;
launchEditor(req.query.fileName, lineNumber, colNumber);
diff --git a/packages/react-dev-utils/noopServiceWorkerMiddleware.js b/packages/react-dev-utils/noopServiceWorkerMiddleware.js
index 568ff4d6519..ad32c09b230 100644
--- a/packages/react-dev-utils/noopServiceWorkerMiddleware.js
+++ b/packages/react-dev-utils/noopServiceWorkerMiddleware.js
@@ -7,9 +7,11 @@
'use strict';
-module.exports = function createNoopServiceWorkerMiddleware() {
+module.exports = function createNoopServiceWorkerMiddleware(
+ servedPathPathname
+) {
return function noopServiceWorkerMiddleware(req, res, next) {
- if (req.url === '/service-worker.js') {
+ if (req.url === `${servedPathPathname}/service-worker.js`) {
res.setHeader('Content-Type', 'text/javascript');
res.send(
`// This service worker file is effectively a 'no-op' that will reset any
diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json
index 48197f822c3..8f40e19955e 100644
--- a/packages/react-dev-utils/package.json
+++ b/packages/react-dev-utils/package.json
@@ -16,6 +16,7 @@
"clearConsole.js",
"crossSpawn.js",
"errorOverlayMiddleware.js",
+ "serveAppMiddleware.js",
"eslintFormatter.js",
"FileSizeReporter.js",
"formatWebpackMessages.js",
diff --git a/packages/react-dev-utils/serveAppMiddleware.js b/packages/react-dev-utils/serveAppMiddleware.js
new file mode 100644
index 00000000000..c2824a96d32
--- /dev/null
+++ b/packages/react-dev-utils/serveAppMiddleware.js
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+'use strict';
+
+module.exports = function createServeAppMiddleware(servedPathPathname) {
+ return function serveAppMiddleware(req, res, next) {
+ if (servedPathPathname.length > 1 && servedPathPathname !== './') {
+ if (req.url.indexOf(servedPathPathname) === -1) {
+ res.redirect(servedPathPathname);
+ } else {
+ next();
+ }
+ } else {
+ next();
+ }
+ };
+};
diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js
index 7a1b66803bc..8bf6ea2bac5 100644
--- a/packages/react-scripts/config/webpack.config.dev.js
+++ b/packages/react-scripts/config/webpack.config.dev.js
@@ -10,6 +10,7 @@
const autoprefixer = require('autoprefixer');
const path = require('path');
+const url = require('url');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
@@ -22,14 +23,18 @@ const getClientEnvironment = require('./env');
const paths = require('./paths');
// Webpack uses `publicPath` to determine where the app is being served from.
-// In development, we always serve from the root. This makes config easier.
-const publicPath = '/';
+// In development, we serve from the root by default. Webpack will serve from
+// the relative path of the homepage field if specified.
+let publicPath = url.parse(paths.servedPath).pathname || '';
+if (publicPath === './') {
+ publicPath = publicPath.slice(1);
+}
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
-const publicUrl = '';
+const publicUrl = paths.servedPath.slice(0, -1) + '/static';
// Get environment variables to inject into our app.
-const env = getClientEnvironment(publicUrl);
+const env = getClientEnvironment(publicUrl === '.' ? '' : publicUrl);
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
@@ -111,6 +116,7 @@ module.exports = {
// There are also additional JS chunk files if you use code splitting.
chunkFilename: 'static/js/[name].chunk.js',
// This is the URL that app is served from. We use "/" in development.
+ // If there is a homepage path defined, it will be served from that instead.
publicPath: publicPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: info =>
diff --git a/packages/react-scripts/config/webpackDevServer.config.js b/packages/react-scripts/config/webpackDevServer.config.js
index 74119d27f39..69fe86d8448 100644
--- a/packages/react-scripts/config/webpackDevServer.config.js
+++ b/packages/react-scripts/config/webpackDevServer.config.js
@@ -10,12 +10,16 @@
const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
+const serveAppMiddleware = require('react-dev-utils/serveAppMiddleware');
const ignoredFiles = require('react-dev-utils/ignoredFiles');
+const url = require('url');
const config = require('./webpack.config.dev');
const paths = require('./paths');
+const express = require('express');
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const host = process.env.HOST || '0.0.0.0';
+const servedPathPathname = url.parse(paths.servedPath).pathname || '';
module.exports = function(proxy, allowedHost) {
return {
@@ -86,18 +90,26 @@ module.exports = function(proxy, allowedHost) {
// Paths with dots should still use the history fallback.
// See https://github.com/facebook/create-react-app/issues/387.
disableDotRule: true,
+ index: servedPathPathname,
},
public: allowedHost,
proxy,
before(app) {
// This lets us open files from the runtime error overlay.
- app.use(errorOverlayMiddleware());
+ app.use(errorOverlayMiddleware(servedPathPathname));
// This service worker file is effectively a 'no-op' that will reset any
// previous service worker registered for the same host:port combination.
// We do this in development to avoid hitting the production cache if
// it used the same host and port.
// https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
- app.use(noopServiceWorkerMiddleware());
+ app.use(noopServiceWorkerMiddleware(servedPathPathname));
+ // serves the app up from the homepage if specified
+ app.use(serveAppMiddleware(servedPathPathname));
+ // serve up static assets
+ app.use(
+ `${config.output.publicPath.slice(0, -1)}/static`,
+ express.static(paths.appPublic)
+ );
},
};
};
diff --git a/packages/react-scripts/fixtures/kitchensink/integration/env.test.js b/packages/react-scripts/fixtures/kitchensink/integration/env.test.js
index 43badcbde8e..ee7f27c383f 100644
--- a/packages/react-scripts/fixtures/kitchensink/integration/env.test.js
+++ b/packages/react-scripts/fixtures/kitchensink/integration/env.test.js
@@ -50,7 +50,7 @@ describe('Integration', () => {
const prefix =
process.env.NODE_ENV === 'development'
- ? ''
+ ? '/static'
: 'http://www.example.org/spa';
expect(doc.getElementById('feature-public-url').textContent).to.equal(
`${prefix}.`
diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js
index 95502bc2a24..127cc4b4c92 100644
--- a/packages/react-scripts/scripts/start.js
+++ b/packages/react-scripts/scripts/start.js
@@ -29,6 +29,7 @@ if (process.env.SKIP_PREFLIGHT_CHECK !== 'true') {
}
// @remove-on-eject-end
+const url = require('url');
const chalk = require('chalk');
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
@@ -56,6 +57,8 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
const HOST = process.env.HOST || '0.0.0.0';
+const servedPathPathname = url.parse(paths.servedPath).pathname || '';
+
if (process.env.HOST) {
console.log(
chalk.cyan(
@@ -89,7 +92,7 @@ checkBrowsers(paths.appPath)
}
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const appName = require(paths.appPackageJson).name;
- const urls = prepareUrls(protocol, HOST, port);
+ const urls = prepareUrls(protocol, HOST, port, servedPathPathname);
// Create a webpack compiler that is configured with custom messages.
const compiler = createCompiler(
webpack,
diff --git a/packages/react-scripts/template/README.md b/packages/react-scripts/template/README.md
index 21cf719e961..c30d9031817 100644
--- a/packages/react-scripts/template/README.md
+++ b/packages/react-scripts/template/README.md
@@ -2192,6 +2192,13 @@ To override this, specify the `homepage` in your `package.json`, for example:
This will let Create React App correctly infer the root path to use in the generated HTML file.
+If `homepage` is specified, Create React App will open your browser at the path specified. From the example above, `npm start` would result in:
+
+```js
+http://localhost:3000/relativepath
+```
+This also means that in development the paths to the static files will be served out of the `relativepath` directory.
+
**Note**: If you are using `react-router@^4`, you can root ``s using the `basename` prop on any ``.
More information [here](https://reacttraining.com/react-router/web/api/BrowserRouter/basename-string).