diff --git a/packages/react-dev-utils/README.md b/packages/react-dev-utils/README.md index 5f64db3bde4..46cdecf188c 100644 --- a/packages/react-dev-utils/README.md +++ b/packages/react-dev-utils/README.md @@ -291,10 +291,10 @@ On macOS, tries to find a known running editor process and opens the file in it. Returns Express middleware that serves a `${servedPath}/service-worker.js` that resets any previously set service worker configuration. Useful for development. -#### `redirectServedPathMiddleware(servedPath: string): ExpressMiddleware` +#### `redirectServedPathMiddleware(servedPath: string, appPublic: string): ExpressMiddleware` Returns Express middleware that redirects to `${servedPath}/${req.path}`, if `req.url` -does not start with `servedPath`. Useful for development. +does not start with `servedPath` or is not a proxy like request. Useful for development. #### `openBrowser(url: string): boolean` @@ -345,10 +345,14 @@ The `args` object accepts a number of properties: - **tscCompileOnError** `boolean`: If `true`, errors in TypeScript type checking will not prevent start script from running app, and will not cause build script to exit unsuccessfully. Also downgrades all TypeScript type checking error messages to warning messages. - **webpack** `function`: A reference to the webpack constructor. -##### `prepareProxy(proxySetting: string, appPublicFolder: string): Object` +##### `prepareProxy(proxySetting: string, appPublicFolder: string, servedPathname: string): Object` Creates a WebpackDevServer `proxy` configuration object from the `proxy` setting in `package.json`. +##### `mayProxy(requestMethod: string, acceptHeader: string | undefined, pathname: string, appPublicFolder: string, servedPathname: string): boolean` + +Will return false if is not a development resource like, a static file, or WDS socket + ##### `prepareUrls(protocol: string, host: string, port: number, pathname: string = '/'): Object` Returns an object with local and remote URLs for the development server. Pass this object to `createCompiler()` described above. diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js index 26b36fca395..f6b0d1ac0f1 100644 --- a/packages/react-dev-utils/WebpackDevServerUtils.js +++ b/packages/react-dev-utils/WebpackDevServerUtils.js @@ -356,7 +356,39 @@ function onProxyError(proxy) { }; } -function prepareProxy(proxy, appPublicFolder) { +function mayProxy( + requestMethod, + acceptHeader, + pathname, + appPublicFolder, + servedPathname +) { + // If proxy is specified, let it handle any request except for + // files in the public folder and requests to the WebpackDevServer socket endpoint. + // https://github.com/facebook/create-react-app/issues/6720 + const sockPath = process.env.WDS_SOCKET_PATH || '/sockjs-node'; + const isDefaultSockHost = !process.env.WDS_SOCKET_HOST; + function isProxy(pathname) { + const maybePublicPath = path.resolve( + appPublicFolder, + pathname.replace(new RegExp('^' + servedPathname), '') + ); + const isPublicFileRequest = fs.existsSync(maybePublicPath); + // used by webpackHotDevClient + const isWdsEndpointRequest = + isDefaultSockHost && pathname.startsWith(sockPath); + return !(isPublicFileRequest || isWdsEndpointRequest); + } + + return ( + requestMethod !== 'GET' || + (isProxy(pathname) && + acceptHeader && + acceptHeader.indexOf('text/html') === -1) + ); +} + +function prepareProxy(proxy, appPublicFolder, servedPathname) { // `proxy` lets you specify alternate servers for specific requests. if (!proxy) { return undefined; @@ -374,20 +406,6 @@ function prepareProxy(proxy, appPublicFolder) { process.exit(1); } - // If proxy is specified, let it handle any request except for - // files in the public folder and requests to the WebpackDevServer socket endpoint. - // https://github.com/facebook/create-react-app/issues/6720 - const sockPath = process.env.WDS_SOCKET_PATH || '/sockjs-node'; - const isDefaultSockHost = !process.env.WDS_SOCKET_HOST; - function mayProxy(pathname) { - const maybePublicPath = path.resolve(appPublicFolder, pathname.slice(1)); - const isPublicFileRequest = fs.existsSync(maybePublicPath); - // used by webpackHotDevClient - const isWdsEndpointRequest = - isDefaultSockHost && pathname.startsWith(sockPath); - return !(isPublicFileRequest || isWdsEndpointRequest); - } - if (!/^http(s)?:\/\//.test(proxy)) { console.log( chalk.red( @@ -418,11 +436,15 @@ function prepareProxy(proxy, appPublicFolder) { // However API calls like `fetch()` won’t generally accept text/html. // If this heuristic doesn’t work well for you, use `src/setupProxy.js`. context: function(pathname, req) { - return ( - req.method !== 'GET' || - (mayProxy(pathname) && - req.headers.accept && - req.headers.accept.indexOf('text/html') === -1) + // If proxy is specified, let it handle any request except for + // files in the public folder and requests to the WebpackDevServer socket endpoint. + // https://github.com/facebook/create-react-app/issues/6720 + return mayProxy( + req.method, + req.headers.accept, + pathname, + appPublicFolder, + servedPathname ); }, onProxyReq: proxyReq => { @@ -492,6 +514,7 @@ function choosePort(host, defaultPort) { module.exports = { choosePort, createCompiler, + mayProxy, prepareProxy, prepareUrls, }; diff --git a/packages/react-dev-utils/redirectServedPathMiddleware.js b/packages/react-dev-utils/redirectServedPathMiddleware.js index a71578f0a9f..3a0d315f508 100644 --- a/packages/react-dev-utils/redirectServedPathMiddleware.js +++ b/packages/react-dev-utils/redirectServedPathMiddleware.js @@ -7,19 +7,34 @@ 'use strict'; const path = require('path'); +const { mayProxy } = require('react-dev-utils/WebpackDevServerUtils'); -module.exports = function createRedirectServedPathMiddleware(servedPath) { +module.exports = function createRedirectServedPathMiddleware( + servedPath, + appPublic +) { // remove end slash so user can land on `/test` instead of `/test/` - servedPath = servedPath.slice(0, -1); + const servedPathSlashTrimmed = servedPath.slice(0, -1); return function redirectServedPathMiddleware(req, res, next) { + const pathname = new URL(req.url, 'https://stub-domain').pathname; if ( - servedPath === '' || - req.url === servedPath || - req.url.startsWith(servedPath) + mayProxy(req.method, req.headers.accept, pathname, appPublic, servedPath) + ) { + next(); + return; + } + + if ( + servedPathSlashTrimmed === '' || + req.url === servedPathSlashTrimmed || + req.url.startsWith(servedPathSlashTrimmed) ) { next(); } else { - const newPath = path.join(servedPath, req.path !== '/' ? req.path : ''); + const newPath = path.join( + servedPathSlashTrimmed, + req.path !== '/' ? req.path : '' + ); res.redirect(newPath); } }; diff --git a/packages/react-scripts/config/webpackDevServer.config.js b/packages/react-scripts/config/webpackDevServer.config.js index 82e78c09f79..ad42332e1a8 100644 --- a/packages/react-scripts/config/webpackDevServer.config.js +++ b/packages/react-scripts/config/webpackDevServer.config.js @@ -108,6 +108,7 @@ module.exports = function(proxy, allowedHost) { index: paths.publicUrlOrPath, }, public: allowedHost, + // `proxy` is run between `before` and `after` `webpack-dev-server` hooks proxy, before(app, server) { // Keep `evalSourceMapMiddleware` and `errorOverlayMiddleware` @@ -117,14 +118,14 @@ module.exports = function(proxy, allowedHost) { // This lets us open files from the runtime error overlay. app.use(errorOverlayMiddleware()); - // Redirect to `PUBLIC_URL` or `homepage` from `package.json` if url not match - app.use(redirectServedPath(paths.publicUrlOrPath)); - if (fs.existsSync(paths.proxySetup)) { // This registers user provided middleware for proxy reasons require(paths.proxySetup)(app); } + // Redirect to `PUBLIC_URL` or `homepage` from `package.json` if url not match + app.use(redirectServedPath(paths.publicUrlOrPath, paths.appPublic)); + // 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 diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index 094f6e80409..97ad10fe001 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -122,7 +122,11 @@ checkBrowsers(paths.appPath, isInteractive) }); // Load proxy config const proxySetting = require(paths.appPackageJson).proxy; - const proxyConfig = prepareProxy(proxySetting, paths.appPublic); + const proxyConfig = prepareProxy( + proxySetting, + paths.appPublic, + paths.publicUrlOrPath + ); // Serve webpack assets generated by the compiler over a web server. const serverConfig = createDevServerConfig( proxyConfig,