From eb0f244a6b7d80a878aec9cf377db1604d8fc78b Mon Sep 17 00:00:00 2001 From: Charles Lyding Date: Mon, 15 May 2017 11:03:32 -0400 Subject: [PATCH] feat(@angular/cli): add option to set dev server's base serve path --- packages/@angular/cli/commands/serve.ts | 6 ++ packages/@angular/cli/tasks/serve.ts | 66 +++++++++++++++++--- tests/e2e/tests/commands/serve/serve-path.ts | 29 +++++++++ 3 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 tests/e2e/tests/commands/serve/serve-path.ts diff --git a/packages/@angular/cli/commands/serve.ts b/packages/@angular/cli/commands/serve.ts index 85fa09caba03..6ab9f9bc4360 100644 --- a/packages/@angular/cli/commands/serve.ts +++ b/packages/@angular/cli/commands/serve.ts @@ -26,6 +26,7 @@ export interface ServeTaskOptions extends BuildOptions { sslCert?: string; open?: boolean; hmr?: boolean; + servePath?: string; } // Expose options unrelated to live-reload to other commands that need to run serve @@ -96,6 +97,11 @@ export const baseServeCommandOptions: any = overrideOptions([ default: false, description: 'Don\'t verify connected clients are part of allowed hosts.', }, + { + name: 'serve-path', + type: String, + description: 'The pathname where the app will be served.' + }, { name: 'hmr', type: Boolean, diff --git a/packages/@angular/cli/tasks/serve.ts b/packages/@angular/cli/tasks/serve.ts index c258888ff0d7..232cc0dd37a0 100644 --- a/packages/@angular/cli/tasks/serve.ts +++ b/packages/@angular/cli/tasks/serve.ts @@ -17,6 +17,39 @@ const SilentError = require('silent-error'); const opn = require('opn'); const yellow = require('chalk').yellow; +function findDefaultServePath(baseHref: string, deployUrl: string): string | null { + if (!baseHref && !deployUrl) { + return ''; + } + + if (/^(\w+:)?\/\//.test(baseHref) || /^(\w+:)?\/\//.test(deployUrl)) { + // If baseHref or deployUrl is absolute, unsupported by ng serve + return null; + } + + // normalize baseHref + // for ng serve the starting base is always `/` so a relative + // and root relative value are identical + const baseHrefParts = (baseHref || '') + .split('/') + .filter(part => part !== ''); + if (baseHref && !baseHref.endsWith('/')) { + baseHrefParts.pop(); + } + const normalizedBaseHref = baseHrefParts.length === 0 ? '/' : `/${baseHrefParts.join('/')}/`; + + if (deployUrl && deployUrl[0] === '/') { + if (baseHref && baseHref[0] === '/' && normalizedBaseHref !== deployUrl) { + // If baseHref and deployUrl are root relative and not equivalent, unsupported by ng serve + return null; + } + return deployUrl; + } + + // Join together baseHref and deployUrl + return `${normalizedBaseHref}${deployUrl || ''}`; +} + export default Task.extend({ run: function (serveTaskOptions: ServeTaskOptions, rebuildDoneCb: any) { const ui = this.ui; @@ -156,10 +189,29 @@ export default Task.extend({ } } + let servePath = serveTaskOptions.servePath; + if (!servePath && servePath !== '') { + const defaultServePath = + findDefaultServePath(serveTaskOptions.baseHref, serveTaskOptions.deployUrl); + if (defaultServePath == null) { + ui.writeLine(oneLine` + ${chalk.yellow('WARNING')} --deploy-url and/or --base-href contain + unsupported values for ng serve. Default serve path of '/' used. + Use --serve-path to override. + `); + } + servePath = defaultServePath || ''; + } + if (servePath.endsWith('/')) { + servePath = servePath.substr(0, servePath.length - 1); + } + if (!servePath.startsWith('/')) { + servePath = `/${servePath}`; + } const webpackDevServerConfiguration: IWebpackDevServerConfigurationOptions = { headers: { 'Access-Control-Allow-Origin': '*' }, historyApiFallback: { - index: `/${appConfig.index}`, + index: `${servePath}/${appConfig.index}`, disableDotRule: true, htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'] }, @@ -177,7 +229,8 @@ export default Task.extend({ }, contentBase: false, public: serveTaskOptions.publicHost, - disableHostCheck: serveTaskOptions.disableHostCheck + disableHostCheck: serveTaskOptions.disableHostCheck, + publicPath: servePath }; if (sslKey != null && sslCert != null) { @@ -187,13 +240,6 @@ export default Task.extend({ webpackDevServerConfiguration.hot = serveTaskOptions.hmr; - // set publicPath property to be sent on webpack server config - if (serveTaskOptions.deployUrl) { - webpackDevServerConfiguration.publicPath = serveTaskOptions.deployUrl; - (webpackDevServerConfiguration.historyApiFallback as any).index = - serveTaskOptions.deployUrl + `/${appConfig.index}`; - } - if (serveTaskOptions.target === 'production') { ui.writeLine(chalk.red(stripIndents` **************************************************************************************** @@ -208,7 +254,7 @@ export default Task.extend({ ui.writeLine(chalk.green(oneLine` ** NG Live Development Server is listening on ${serveTaskOptions.host}:${serveTaskOptions.port}, - open your browser on ${serverAddress} + open your browser on ${serverAddress}${servePath} ** `)); diff --git a/tests/e2e/tests/commands/serve/serve-path.ts b/tests/e2e/tests/commands/serve/serve-path.ts new file mode 100644 index 000000000000..3a58e9cca5f6 --- /dev/null +++ b/tests/e2e/tests/commands/serve/serve-path.ts @@ -0,0 +1,29 @@ +import { request } from '../../../utils/http'; +import { killAllProcesses } from '../../../utils/process'; +import { ngServe } from '../../../utils/project'; + +export default function () { + return Promise.resolve() + .then(() => ngServe('--serve-path', 'test/')) + .then(() => request('http://localhost:4200/test')) + .then(body => { + if (!body.match(/<\/app-root>/)) { + throw new Error('Response does not match expected value.'); + } + }) + .then(() => request('http://localhost:4200/test/abc')) + .then(body => { + if (!body.match(/<\/app-root>/)) { + throw new Error('Response does not match expected value.'); + } + }) + .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }) + .then(() => ngServe('--base-href', 'test/')) + .then(() => request('http://localhost:4200/test')) + .then(body => { + if (!body.match(/<\/app-root>/)) { + throw new Error('Response does not match expected value.'); + } + }) + .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }); +}