Skip to content

refactor: move runBonjourand runOpen utils to Server #3392

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 12 commits into from
Jun 21, 2021
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
284 changes: 225 additions & 59 deletions lib/Server.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

const path = require('path');
const fs = require('fs');
const url = require('url');
const ipaddr = require('ipaddr.js');
const internalIp = require('internal-ip');
Expand All @@ -11,9 +13,7 @@ const colors = require('./utils/colors');
const routes = require('./utils/routes');
const getSocketServerImplementation = require('./utils/getSocketServerImplementation');
const getCompilerConfigArray = require('./utils/getCompilerConfigArray');
const setupExitSignals = require('./utils/setupExitSignals');
const getStatsOption = require('./utils/getStatsOption');
const getColorsOption = require('./utils/getColorsOption');
const schema = require('./options.json');

if (!process.env.WEBPACK_SERVE) {
Expand All @@ -39,7 +39,12 @@ class Server {
// this value of web socket can be overwritten for tests
this.webSocketHeartbeatInterval = 30000;

normalizeOptions(this.compiler, this.options, this.logger);
normalizeOptions(
this.compiler,
this.options,
this.logger,
Server.findCacheDir()
);

this.applyDevServerPlugin();

Expand All @@ -64,7 +69,19 @@ class Server {
this.createServer();

killable(this.server);
setupExitSignals(this);

if (this.options.setupExitSignals) {
const signals = ['SIGINT', 'SIGTERM'];

signals.forEach((signal) => {
process.on(signal, () => {
this.close(() => {
// eslint-disable-next-line no-process-exit
process.exit();
});
});
});
}

// Proxy WebSocket without the initial http request
// https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
Expand All @@ -74,6 +91,92 @@ class Server {
}, this);
}

static get DEFAULT_STATS() {
return {
all: false,
hash: true,
assets: true,
warnings: true,
errors: true,
errorDetails: false,
};
}

static getHostname(hostname) {
if (hostname === 'local-ip') {
return internalIp.v4.sync() || internalIp.v6.sync() || '0.0.0.0';
} else if (hostname === 'local-ipv4') {
return internalIp.v4.sync() || '0.0.0.0';
} else if (hostname === 'local-ipv6') {
return internalIp.v6.sync() || '::';
}

return hostname;
}

static getFreePort(port) {
const pRetry = require('p-retry');
const portfinder = require('portfinder');

if (port && port !== 'auto') {
return Promise.resolve(port);
}

function runPortFinder() {
return new Promise((resolve, reject) => {
// Default port
portfinder.basePort = process.env.WEBPACK_DEV_SERVER_BASE_PORT || 8080;
portfinder.getPort((error, foundPort) => {
if (error) {
return reject(error);
}

return resolve(foundPort);
});
});
}

// Try to find unused port and listen on it for 3 times,
// if port is not specified in options.
const defaultPortRetry =
parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10) || 3;

return pRetry(runPortFinder, { retries: defaultPortRetry });
}

static findCacheDir() {
const cwd = process.cwd();

let dir = cwd;

for (;;) {
try {
if (fs.statSync(path.join(dir, 'package.json')).isFile()) break;
// eslint-disable-next-line no-empty
} catch (e) {}

const parent = path.dirname(dir);

if (dir === parent) {
// eslint-disable-next-line no-undefined
dir = undefined;
break;
}

dir = parent;
}

if (!dir) {
return path.resolve(cwd, '.cache/webpack-dev-server');
} else if (process.versions.pnp === '1') {
return path.resolve(dir, '.pnp/.cache/webpack-dev-server');
} else if (process.versions.pnp === '3') {
return path.resolve(dir, '.yarn/.cache/webpack-dev-server');
}

return path.resolve(dir, 'node_modules/.cache/webpack-dev-server');
}

applyDevServerPlugin() {
const DevServerPlugin = require('./utils/DevServerPlugin');

Expand Down Expand Up @@ -539,7 +642,122 @@ class Server {
});
}

openBrowser(uri) {
const isAbsoluteUrl = require('is-absolute-url');
const open = require('open');

// https://github.com/webpack/webpack-dev-server/issues/1990
const defaultOpenOptions = { wait: false };
const openTasks = [];

const getOpenTask = (item) => {
if (typeof item === 'boolean') {
return [{ target: uri, options: defaultOpenOptions }];
}

if (typeof item === 'string') {
return [{ target: item, options: defaultOpenOptions }];
}

let targets;

if (item.target) {
targets = Array.isArray(item.target) ? item.target : [item.target];
} else {
targets = [uri];
}

return targets.map((target) => {
const openOptions = defaultOpenOptions;

if (item.app) {
if (typeof item.app === 'string') {
openOptions.app = { name: item.app };
} else {
openOptions.app = item.app;
}
}

return { target, options: openOptions };
});
};

if (Array.isArray(this.options.open)) {
this.options.open.forEach((item) => {
openTasks.push(...getOpenTask(item));
});
} else {
openTasks.push(...getOpenTask(this.options.open));
}

Promise.all(
openTasks.map((openTask) => {
let openTarget;

if (openTask.target) {
if (typeof openTask.target === 'boolean') {
openTarget = uri;
} else {
openTarget = isAbsoluteUrl(openTask.target)
? openTask.target
: new URL(openTask.target, uri).toString();
}
} else {
openTarget = uri;
}

return open(openTarget, openTask.options).catch(() => {
this.logger.warn(
`Unable to open "${openTarget}" page${
// eslint-disable-next-line no-nested-ternary
openTask.options.app
? ` in "${openTask.options.app.name}" app${
openTask.options.app.arguments
? ` with "${openTask.options.app.arguments.join(
' '
)}" arguments`
: ''
}`
: ''
}. If you are running in a headless environment, please do not use the "open" option or related flags like "--open", "--open-target", and "--open-app".`
);
});
})
);
}

runBonjour() {
const bonjour = require('bonjour')();
const os = require('os');

bonjour.publish({
name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`,
port: this.options.port,
type: this.options.https ? 'https' : 'http',
subtypes: ['webpack'],
...this.options.bonjour,
});

process.on('exit', () => {
bonjour.unpublishAll(() => {
bonjour.destroy();
});
});
}

logStatus() {
const getColorsOption = (configArray) => {
const statsOption = getStatsOption(configArray);

let colorsEnabled = false;

if (typeof statsOption === 'object' && statsOption.colors) {
colorsEnabled = statsOption.colors;
}

return colorsEnabled;
};

const useColor = getColorsOption(getCompilerConfigArray(this.compiler));
const protocol = this.options.https ? 'https' : 'http';
const { address, port } = this.server.address();
Expand Down Expand Up @@ -661,11 +879,9 @@ class Server {
}

if (this.options.open) {
const runOpen = require('./utils/runOpen');

const openTarget = prettyPrintUrl(this.options.host || 'localhost');

runOpen(openTarget, this.options.open, this.logger);
this.openBrowser(openTarget);
}
}

Expand Down Expand Up @@ -702,14 +918,7 @@ class Server {
this.options.host = hostname;
}

if (this.options.host === 'local-ip') {
this.options.host =
internalIp.v4.sync() || internalIp.v6.sync() || '0.0.0.0';
} else if (this.options.host === 'local-ipv4') {
this.options.host = internalIp.v4.sync() || '0.0.0.0';
} else if (this.options.host === 'local-ipv6') {
this.options.host = internalIp.v6.sync() || '::';
}
this.options.host = Server.getHostname(this.options.host);

return Server.getFreePort(this.options.port)
.then((foundPort) => {
Expand All @@ -724,9 +933,7 @@ class Server {
}

if (this.options.bonjour) {
const runBonjour = require('./utils/runBonjour');

runBonjour(this.options);
this.runBonjour();
}

this.logStatus();
Expand Down Expand Up @@ -767,47 +974,6 @@ class Server {
});
}

static get DEFAULT_STATS() {
return {
all: false,
hash: true,
assets: true,
warnings: true,
errors: true,
errorDetails: false,
};
}

static getFreePort(port) {
const pRetry = require('p-retry');
const portfinder = require('portfinder');

if (port && port !== 'auto') {
return Promise.resolve(port);
}

function runPortFinder() {
return new Promise((resolve, reject) => {
// default port
portfinder.basePort = process.env.WEBPACK_DEV_SERVER_BASE_PORT || 8080;
portfinder.getPort((error, foundPort) => {
if (error) {
return reject(error);
}

return resolve(foundPort);
});
});
}

// Try to find unused port and listen on it for 3 times,
// if port is not specified in options.
const defaultPortRetry =
parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10) || 3;

return pRetry(runPortFinder, { retries: defaultPortRetry });
}

getStats(statsObj) {
const stats = Server.DEFAULT_STATS;

Expand Down
Loading