Skip to content

feat(@angular/cli): add option to set dev server's base serve path #6320

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from Aug 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/@angular/cli/commands/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
66 changes: 56 additions & 10 deletions packages/@angular/cli/tasks/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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']
},
Expand All @@ -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) {
Expand All @@ -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`
****************************************************************************************
Expand All @@ -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}
**
`));

Expand Down
29 changes: 29 additions & 0 deletions tests/e2e/tests/commands/serve/serve-path.ts
Original file line number Diff line number Diff line change
@@ -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><\/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><\/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><\/app-root>/)) {
throw new Error('Response does not match expected value.');
}
})
.then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; });
}