Skip to content

Commit 2c6dde1

Browse files
Charles LydingBrocco
Charles Lyding
authored andcommitted
feat(@angular/cli): add option to set dev server's base serve path
1 parent 6b115dc commit 2c6dde1

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
@@ -17,6 +17,39 @@ const SilentError = require('silent-error');
1717
const opn = require('opn');
1818
const yellow = require('chalk').yellow;
1919

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

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

183236
if (sslKey != null && sslCert != null) {
@@ -187,13 +240,6 @@ export default Task.extend({
187240

188241
webpackDevServerConfiguration.hot = serveTaskOptions.hmr;
189242

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

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)