Skip to content

Commit 404ff9f

Browse files
author
Charles Lyding
committed
feat(@angular/cli): add option to set dev server's base serve path
1 parent 6f23636 commit 404ff9f

File tree

3 files changed

+91
-10
lines changed

3 files changed

+91
-10
lines changed

packages/@angular/cli/commands/serve.ts

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface ServeTaskOptions extends BuildOptions {
2626
sslCert?: string;
2727
open?: boolean;
2828
hmr?: boolean;
29+
servePath?: string;
2930
}
3031

3132
// Expose options unrelated to live-reload to other commands that need to run serve
@@ -96,6 +97,11 @@ export const baseServeCommandOptions: any = overrideOptions([
9697
default: false,
9798
description: 'Don\'t verify connected clients are part of allowed hosts.',
9899
},
100+
{
101+
name: 'serve-path',
102+
type: String,
103+
description: 'The pathname where the app will be served.'
104+
},
99105
{
100106
name: 'hmr',
101107
type: Boolean,

packages/@angular/cli/tasks/serve.ts

+56-10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,39 @@ const SilentError = require('silent-error');
1616
const opn = require('opn');
1717
const yellow = require('chalk').yellow;
1818

19+
function findDefaultServePath(baseHref: string, deployUrl: string): string | null {
20+
if (!baseHref && !deployUrl) {
21+
return '';
22+
}
23+
24+
if (/^(\w+:)?\/\//.test(baseHref) || /^(\w+:)?\/\//.test(deployUrl)) {
25+
// If baseHref or deployUrl is absolute, unsupported by ng serve
26+
return null;
27+
}
28+
29+
// normalize baseHref
30+
// for ng serve the starting base is always `/` so a relative
31+
// and root relative value are identical
32+
const baseHrefParts = (baseHref || '')
33+
.split('/')
34+
.filter(part => part !== '');
35+
if (baseHref && !baseHref.endsWith('/')) {
36+
baseHrefParts.pop();
37+
}
38+
const normalizedBaseHref = baseHrefParts.length === 0 ? '/' : `/${baseHrefParts.join('/')}/`;
39+
40+
if (deployUrl && deployUrl[0] === '/') {
41+
if (baseHref && baseHref[0] === '/' && normalizedBaseHref !== deployUrl) {
42+
// If baseHref and deployUrl are root relative and not equivalent, unsupported by ng serve
43+
return null;
44+
}
45+
return deployUrl;
46+
}
47+
48+
// Join together baseHref and deployUrl
49+
return `${normalizedBaseHref}${deployUrl || ''}`;
50+
}
51+
1952
export default Task.extend({
2053
run: function (serveTaskOptions: ServeTaskOptions, rebuildDoneCb: any) {
2154
const ui = this.ui;
@@ -155,10 +188,29 @@ export default Task.extend({
155188
}
156189
}
157190

191+
let servePath = serveTaskOptions.servePath;
192+
if (!servePath && servePath !== '') {
193+
const defaultServePath =
194+
findDefaultServePath(serveTaskOptions.baseHref, serveTaskOptions.deployUrl);
195+
if (defaultServePath == null) {
196+
ui.writeLine(oneLine`
197+
${chalk.yellow('WARNING')} --deploy-url and/or --base-href contain
198+
unsupported values for ng serve. Default serve path of '/' used.
199+
Use --serve-path to override.
200+
`);
201+
}
202+
servePath = defaultServePath || '';
203+
}
204+
if (servePath.endsWith('/')) {
205+
servePath = servePath.substr(0, servePath.length - 1);
206+
}
207+
if (!servePath.startsWith('/')) {
208+
servePath = `/${servePath}`;
209+
}
158210
const webpackDevServerConfiguration: IWebpackDevServerConfigurationOptions = {
159211
headers: { 'Access-Control-Allow-Origin': '*' },
160212
historyApiFallback: {
161-
index: `/${appConfig.index}`,
213+
index: `${servePath}/${appConfig.index}`,
162214
disableDotRule: true,
163215
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml']
164216
},
@@ -176,7 +228,8 @@ export default Task.extend({
176228
},
177229
contentBase: false,
178230
public: serveTaskOptions.publicHost,
179-
disableHostCheck: serveTaskOptions.disableHostCheck
231+
disableHostCheck: serveTaskOptions.disableHostCheck,
232+
publicPath: servePath
180233
};
181234

182235
if (sslKey != null && sslCert != null) {
@@ -186,13 +239,6 @@ export default Task.extend({
186239

187240
webpackDevServerConfiguration.hot = serveTaskOptions.hmr;
188241

189-
// set publicPath property to be sent on webpack server config
190-
if (serveTaskOptions.deployUrl) {
191-
webpackDevServerConfiguration.publicPath = serveTaskOptions.deployUrl;
192-
(webpackDevServerConfiguration.historyApiFallback as any).index =
193-
serveTaskOptions.deployUrl + `/${appConfig.index}`;
194-
}
195-
196242
if (serveTaskOptions.target === 'production') {
197243
ui.writeLine(chalk.red(stripIndents`
198244
****************************************************************************************
@@ -207,7 +253,7 @@ export default Task.extend({
207253
ui.writeLine(chalk.green(oneLine`
208254
**
209255
NG Live Development Server is listening on ${serveTaskOptions.host}:${serveTaskOptions.port},
210-
open your browser on ${serverAddress}
256+
open your browser on ${serverAddress}${servePath}
211257
**
212258
`));
213259

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { request } from '../../../utils/http';
2+
import { killAllProcesses } from '../../../utils/process';
3+
import { ngServe } from '../../../utils/project';
4+
5+
export default function () {
6+
return Promise.resolve()
7+
.then(() => ngServe('--serve-path', 'test/'))
8+
.then(() => request('http://localhost:4200/test'))
9+
.then(body => {
10+
if (!body.match(/<app-root><\/app-root>/)) {
11+
throw new Error('Response does not match expected value.');
12+
}
13+
})
14+
.then(() => request('http://localhost:4200/test/abc'))
15+
.then(body => {
16+
if (!body.match(/<app-root><\/app-root>/)) {
17+
throw new Error('Response does not match expected value.');
18+
}
19+
})
20+
.then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; })
21+
.then(() => ngServe('--base-href', 'test/'))
22+
.then(() => request('http://localhost:4200/test'))
23+
.then(body => {
24+
if (!body.match(/<app-root><\/app-root>/)) {
25+
throw new Error('Response does not match expected value.');
26+
}
27+
})
28+
.then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; });
29+
}

0 commit comments

Comments
 (0)