Skip to content

Commit

Permalink
fix(serve): allow relevant live-reload options to function (angular#4744
Browse files Browse the repository at this point in the history
)

This provides implementations for the following serve command options:

live-reload [boolean; default: true] -- flag to control the browser live reload capability
live-reload-client [URL; default: ssl/host/port command options] -- specify the URL that the live reload browser client will use

Closes angular#3361
  • Loading branch information
filipesilva authored and Zhicheng Wang committed Mar 16, 2017
1 parent c67607f commit 9a0eaf9
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 133 deletions.
8 changes: 1 addition & 7 deletions docs/documentation/serve.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,7 @@

`--live-reload` (`-lr`) flag to turn off live reloading

`--live-reload-host` (`-lrh`) specify the host for live reloading

`--live-reload-base-url` (`-lrbu`) specify the base URL for live reloading

`--live-reload-port` (`-lrp`) port for live reloading

`--live-reload-live-css` flag to live reload CSS
`--live-reload-client` specify the URL that the live reload browser client will use

`--ssl` flag to turn on SSL

Expand Down
133 changes: 42 additions & 91 deletions packages/@angular/cli/commands/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,19 @@ import { overrideOptions } from '../utilities/override-options';
const SilentError = require('silent-error');
const PortFinder = require('portfinder');
const Command = require('../ember-cli/lib/models/command');
const getPort = <any>denodeify(PortFinder.getPort);

PortFinder.basePort = 49152;
const getPort = denodeify<{ host: string, port: number }, number>(PortFinder.getPort);

const config = CliConfig.fromProject() || CliConfig.fromGlobal();
const defaultPort = process.env.PORT || config.get('defaults.serve.port');
const defaultHost = config.get('defaults.serve.host');
PortFinder.basePort = defaultPort;

export interface ServeTaskOptions extends BuildOptions {
port?: number;
host?: string;
proxyConfig?: string;
liveReload?: boolean;
liveReloadHost?: string;
liveReloadPort?: number;
liveReloadBaseUrl?: string;
liveReloadLiveCss?: boolean;
liveReloadClient?: string;
ssl?: boolean;
sslKey?: string;
sslCert?: string;
Expand All @@ -35,80 +31,57 @@ export interface ServeTaskOptions extends BuildOptions {
}

// Expose options unrelated to live-reload to other commands that need to run serve
export const baseServeCommandOptions: any = baseBuildCommandOptions.concat([
{ name: 'port', type: Number, default: defaultPort, aliases: ['p'] },
{
name: 'host',
type: String,
default: defaultHost,
aliases: ['H'],
description: `Listens only on ${defaultHost} by default`
},
{ name: 'proxy-config', type: 'Path', aliases: ['pc'] },
{ name: 'ssl', type: Boolean, default: false },
{ name: 'ssl-key', type: String, default: 'ssl/server.key' },
{ name: 'ssl-cert', type: String, default: 'ssl/server.crt' },
{
name: 'open',
type: Boolean,
default: false,
aliases: ['o'],
description: 'Opens the url in default browser',
}
]);
export const baseServeCommandOptions: any = overrideOptions(
baseBuildCommandOptions.concat([
{ name: 'port', type: Number, default: defaultPort, aliases: ['p'] },
{
name: 'host',
type: String,
default: defaultHost,
aliases: ['H'],
description: `Listens only on ${defaultHost} by default`
},
{ name: 'proxy-config', type: 'Path', aliases: ['pc'] },
{ name: 'ssl', type: Boolean, default: false },
{ name: 'ssl-key', type: String, default: 'ssl/server.key' },
{ name: 'ssl-cert', type: String, default: 'ssl/server.crt' },
{
name: 'open',
type: Boolean,
default: false,
aliases: ['o'],
description: 'Opens the url in default browser',
},
{ name: 'live-reload', type: Boolean, default: true, aliases: ['lr'] },
{
name: 'live-reload-client',
type: String,
description: 'specify the URL that the live reload browser client will use'
},
{
name: 'hmr',
type: Boolean,
default: false,
description: 'Enable hot module replacement',
}
]), [
{ name: 'watch', default: true },
]
);

const ServeCommand = Command.extend({
name: 'serve',
description: 'Builds and serves your app, rebuilding on file changes.',
aliases: ['server', 's'],

availableOptions: overrideOptions(
baseServeCommandOptions.concat([
{ name: 'live-reload', type: Boolean, default: true, aliases: ['lr'] },
{
name: 'live-reload-host',
type: String,
aliases: ['lrh'],
description: 'Defaults to host'
},
{
name: 'live-reload-base-url',
type: String,
aliases: ['lrbu'],
description: 'Defaults to baseURL'
},
{
name: 'live-reload-port',
type: Number,
aliases: ['lrp'],
description: '(Defaults to port number within [49152...65535])'
},
{
name: 'live-reload-live-css',
type: Boolean,
default: true,
description: 'Whether to live reload CSS (default true)'
},
{
name: 'hmr',
type: Boolean,
default: false,
description: 'Enable hot module replacement',
}
]), [
{ name: 'watch', default: true },
]
),
availableOptions: baseServeCommandOptions,

run: function (commandOptions: ServeTaskOptions) {
const ServeTask = require('../tasks/serve').default;

Version.assertAngularVersionIs2_3_1OrHigher(this.project.root);
commandOptions.liveReloadHost = commandOptions.liveReloadHost || commandOptions.host;

return checkPort(commandOptions.port, commandOptions.host)
.then((port: number) => commandOptions.port = port)
.then(() => autoFindLiveReloadPort(commandOptions))
return checkExpressPort(commandOptions)
.then((opts: ServeTaskOptions) => {
const serve = new ServeTask({
ui: this.ui,
Expand Down Expand Up @@ -137,26 +110,4 @@ function checkExpressPort(commandOptions: ServeTaskOptions) {
});
}

function autoFindLiveReloadPort(commandOptions: ServeTaskOptions) {
return getPort({ port: commandOptions.liveReloadPort, host: commandOptions.liveReloadHost })
.then((foundPort: number) => {

// if live reload port matches express port, try one higher
if (foundPort === commandOptions.port) {
commandOptions.liveReloadPort = foundPort + 1;
return autoFindLiveReloadPort(commandOptions);
}

// port was already open
if (foundPort === commandOptions.liveReloadPort) {
return commandOptions;
}

// use found port as live reload port
commandOptions.liveReloadPort = foundPort;
return commandOptions;

});
}

export default ServeCommand;
89 changes: 54 additions & 35 deletions packages/@angular/cli/tasks/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,33 +41,52 @@ export default Task.extend({

let webpackConfig = new NgCliWebpackConfig(serveTaskOptions).config;

// This allows for live reload of page when changes are made to repo.
// https://webpack.github.io/docs/webpack-dev-server.html#inline-mode
let entryPoints = [
`webpack-dev-server/client?http://${serveTaskOptions.host}:${serveTaskOptions.port}/`
];
if (serveTaskOptions.hmr) {
const webpackHmrLink = 'https://webpack.github.io/docs/hot-module-replacement.html';
ui.writeLine(oneLine`
${chalk.yellow('NOTICE')} Hot Module Replacement (HMR) is enabled for the dev server.
`);
ui.writeLine(' The project will still live reload when HMR is enabled,');
ui.writeLine(' but to take advantage of HMR additional application code is required');
ui.writeLine(' (not included in an Angular CLI project by default).');
ui.writeLine(` See ${chalk.blue(webpackHmrLink)}`);
ui.writeLine(' for information on working with HMR for Webpack.');
entryPoints.push('webpack/hot/dev-server');
webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
webpackConfig.plugins.push(new webpack.NamedModulesPlugin());
if (serveTaskOptions.extractCss) {
const serverAddress = url.format({
protocol: serveTaskOptions.ssl ? 'https' : 'http',
hostname: serveTaskOptions.host,
port: serveTaskOptions.port.toString()
});
let clientAddress = serverAddress;
if (serveTaskOptions.liveReloadClient) {
const clientUrl = url.parse(serveTaskOptions.liveReloadClient);
// very basic sanity check
if (!clientUrl.host) {
return Promise.reject(new SilentError(`'live-reload-client' must be a full URL.`));
}
clientAddress = clientUrl.href;
}

if (serveTaskOptions.liveReload) {
// This allows for live reload of page when changes are made to repo.
// https://webpack.github.io/docs/webpack-dev-server.html#inline-mode
let entryPoints = [
`webpack-dev-server/client?${clientAddress}`
];
if (serveTaskOptions.hmr) {
const webpackHmrLink = 'https://webpack.github.io/docs/hot-module-replacement.html';
ui.writeLine(oneLine`
${chalk.yellow('NOTICE')} (HMR) does not allow for CSS hot reload when used
together with '--extract-css'.
${chalk.yellow('NOTICE')} Hot Module Replacement (HMR) is enabled for the dev server.
`);
ui.writeLine(' The project will still live reload when HMR is enabled,');
ui.writeLine(' but to take advantage of HMR additional application code is required');
ui.writeLine(' (not included in an Angular CLI project by default).');
ui.writeLine(` See ${chalk.blue(webpackHmrLink)}`);
ui.writeLine(' for information on working with HMR for Webpack.');
entryPoints.push('webpack/hot/dev-server');
webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
webpackConfig.plugins.push(new webpack.NamedModulesPlugin());
if (serveTaskOptions.extractCss) {
ui.writeLine(oneLine`
${chalk.yellow('NOTICE')} (HMR) does not allow for CSS hot reload when used
together with '--extract-css'.
`);
}
}
if (!webpackConfig.entry.main) { webpackConfig.entry.main = []; }
webpackConfig.entry.main.unshift(...entryPoints);
} else if (serveTaskOptions.hmr) {
ui.writeLine(chalk.yellow('Live reload is disabled. HMR option ignored.'));
}
if (!webpackConfig.entry.main) { webpackConfig.entry.main = []; }
webpackConfig.entry.main.unshift(...entryPoints);

if (!serveTaskOptions.watch) {
// There's no option to turn off file watching in webpack-dev-server, but
Expand Down Expand Up @@ -151,26 +170,26 @@ export default Task.extend({

ui.writeLine(chalk.green(oneLine`
**
NG Live Development Server is running on
http${serveTaskOptions.ssl ? 's' : ''}://${serveTaskOptions.host}:${serveTaskOptions.port}.
NG Live Development Server is running on ${serverAddress}
**
`));

const server = new WebpackDevServer(webpackCompiler, webpackDevServerConfiguration);
return new Promise((resolve, reject) => {
server.listen(serveTaskOptions.port, `${serveTaskOptions.host}`, (err: any, stats: any) => {
server.listen(serveTaskOptions.port, serveTaskOptions.host, (err: any, stats: any) => {
if (err) {
console.error(err.stack || err);
if (err.details) { console.error(err.details); }
reject(err.details);
} else {
const { open, ssl, host, port } = serveTaskOptions;
if (open) {
let protocol = ssl ? 'https' : 'http';
opn(url.format({ protocol: protocol, hostname: host, port: port.toString() }));
}
return reject(err);
}
if (serveTaskOptions.open) {
opn(serverAddress);
}
});
})
.catch((err: Error) => {
if (err) {
this.ui.writeError('\nAn error occured during the build:\n' + ((err && err.stack) || err));
}
throw err;
});
}
});
Loading

0 comments on commit 9a0eaf9

Please sign in to comment.