Skip to content
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

fix(serve): allow relevant live-reload options to function #4744

Merged
merged 2 commits into from
Feb 21, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
});
}
});