Skip to content

Commit 0020f3e

Browse files
authored
refactor: code (#3392)
1 parent 52c3d69 commit 0020f3e

20 files changed

+511
-478
lines changed

lib/Server.js

+225-59
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
const path = require('path');
4+
const fs = require('fs');
35
const url = require('url');
46
const ipaddr = require('ipaddr.js');
57
const internalIp = require('internal-ip');
@@ -11,9 +13,7 @@ const colors = require('./utils/colors');
1113
const routes = require('./utils/routes');
1214
const getSocketServerImplementation = require('./utils/getSocketServerImplementation');
1315
const getCompilerConfigArray = require('./utils/getCompilerConfigArray');
14-
const setupExitSignals = require('./utils/setupExitSignals');
1516
const getStatsOption = require('./utils/getStatsOption');
16-
const getColorsOption = require('./utils/getColorsOption');
1717
const schema = require('./options.json');
1818

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

42-
normalizeOptions(this.compiler, this.options, this.logger);
42+
normalizeOptions(
43+
this.compiler,
44+
this.options,
45+
this.logger,
46+
Server.findCacheDir()
47+
);
4348

4449
this.applyDevServerPlugin();
4550

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

6671
killable(this.server);
67-
setupExitSignals(this);
72+
73+
if (this.options.setupExitSignals) {
74+
const signals = ['SIGINT', 'SIGTERM'];
75+
76+
signals.forEach((signal) => {
77+
process.on(signal, () => {
78+
this.close(() => {
79+
// eslint-disable-next-line no-process-exit
80+
process.exit();
81+
});
82+
});
83+
});
84+
}
6885

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

94+
static get DEFAULT_STATS() {
95+
return {
96+
all: false,
97+
hash: true,
98+
assets: true,
99+
warnings: true,
100+
errors: true,
101+
errorDetails: false,
102+
};
103+
}
104+
105+
static getHostname(hostname) {
106+
if (hostname === 'local-ip') {
107+
return internalIp.v4.sync() || internalIp.v6.sync() || '0.0.0.0';
108+
} else if (hostname === 'local-ipv4') {
109+
return internalIp.v4.sync() || '0.0.0.0';
110+
} else if (hostname === 'local-ipv6') {
111+
return internalIp.v6.sync() || '::';
112+
}
113+
114+
return hostname;
115+
}
116+
117+
static getFreePort(port) {
118+
const pRetry = require('p-retry');
119+
const portfinder = require('portfinder');
120+
121+
if (port && port !== 'auto') {
122+
return Promise.resolve(port);
123+
}
124+
125+
function runPortFinder() {
126+
return new Promise((resolve, reject) => {
127+
// Default port
128+
portfinder.basePort = process.env.WEBPACK_DEV_SERVER_BASE_PORT || 8080;
129+
portfinder.getPort((error, foundPort) => {
130+
if (error) {
131+
return reject(error);
132+
}
133+
134+
return resolve(foundPort);
135+
});
136+
});
137+
}
138+
139+
// Try to find unused port and listen on it for 3 times,
140+
// if port is not specified in options.
141+
const defaultPortRetry =
142+
parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10) || 3;
143+
144+
return pRetry(runPortFinder, { retries: defaultPortRetry });
145+
}
146+
147+
static findCacheDir() {
148+
const cwd = process.cwd();
149+
150+
let dir = cwd;
151+
152+
for (;;) {
153+
try {
154+
if (fs.statSync(path.join(dir, 'package.json')).isFile()) break;
155+
// eslint-disable-next-line no-empty
156+
} catch (e) {}
157+
158+
const parent = path.dirname(dir);
159+
160+
if (dir === parent) {
161+
// eslint-disable-next-line no-undefined
162+
dir = undefined;
163+
break;
164+
}
165+
166+
dir = parent;
167+
}
168+
169+
if (!dir) {
170+
return path.resolve(cwd, '.cache/webpack-dev-server');
171+
} else if (process.versions.pnp === '1') {
172+
return path.resolve(dir, '.pnp/.cache/webpack-dev-server');
173+
} else if (process.versions.pnp === '3') {
174+
return path.resolve(dir, '.yarn/.cache/webpack-dev-server');
175+
}
176+
177+
return path.resolve(dir, 'node_modules/.cache/webpack-dev-server');
178+
}
179+
77180
applyDevServerPlugin() {
78181
const DevServerPlugin = require('./utils/DevServerPlugin');
79182

@@ -539,7 +642,122 @@ class Server {
539642
});
540643
}
541644

645+
openBrowser(uri) {
646+
const isAbsoluteUrl = require('is-absolute-url');
647+
const open = require('open');
648+
649+
// https://github.com/webpack/webpack-dev-server/issues/1990
650+
const defaultOpenOptions = { wait: false };
651+
const openTasks = [];
652+
653+
const getOpenTask = (item) => {
654+
if (typeof item === 'boolean') {
655+
return [{ target: uri, options: defaultOpenOptions }];
656+
}
657+
658+
if (typeof item === 'string') {
659+
return [{ target: item, options: defaultOpenOptions }];
660+
}
661+
662+
let targets;
663+
664+
if (item.target) {
665+
targets = Array.isArray(item.target) ? item.target : [item.target];
666+
} else {
667+
targets = [uri];
668+
}
669+
670+
return targets.map((target) => {
671+
const openOptions = defaultOpenOptions;
672+
673+
if (item.app) {
674+
if (typeof item.app === 'string') {
675+
openOptions.app = { name: item.app };
676+
} else {
677+
openOptions.app = item.app;
678+
}
679+
}
680+
681+
return { target, options: openOptions };
682+
});
683+
};
684+
685+
if (Array.isArray(this.options.open)) {
686+
this.options.open.forEach((item) => {
687+
openTasks.push(...getOpenTask(item));
688+
});
689+
} else {
690+
openTasks.push(...getOpenTask(this.options.open));
691+
}
692+
693+
Promise.all(
694+
openTasks.map((openTask) => {
695+
let openTarget;
696+
697+
if (openTask.target) {
698+
if (typeof openTask.target === 'boolean') {
699+
openTarget = uri;
700+
} else {
701+
openTarget = isAbsoluteUrl(openTask.target)
702+
? openTask.target
703+
: new URL(openTask.target, uri).toString();
704+
}
705+
} else {
706+
openTarget = uri;
707+
}
708+
709+
return open(openTarget, openTask.options).catch(() => {
710+
this.logger.warn(
711+
`Unable to open "${openTarget}" page${
712+
// eslint-disable-next-line no-nested-ternary
713+
openTask.options.app
714+
? ` in "${openTask.options.app.name}" app${
715+
openTask.options.app.arguments
716+
? ` with "${openTask.options.app.arguments.join(
717+
' '
718+
)}" arguments`
719+
: ''
720+
}`
721+
: ''
722+
}. 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".`
723+
);
724+
});
725+
})
726+
);
727+
}
728+
729+
runBonjour() {
730+
const bonjour = require('bonjour')();
731+
const os = require('os');
732+
733+
bonjour.publish({
734+
name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`,
735+
port: this.options.port,
736+
type: this.options.https ? 'https' : 'http',
737+
subtypes: ['webpack'],
738+
...this.options.bonjour,
739+
});
740+
741+
process.on('exit', () => {
742+
bonjour.unpublishAll(() => {
743+
bonjour.destroy();
744+
});
745+
});
746+
}
747+
542748
logStatus() {
749+
const getColorsOption = (configArray) => {
750+
const statsOption = getStatsOption(configArray);
751+
752+
let colorsEnabled = false;
753+
754+
if (typeof statsOption === 'object' && statsOption.colors) {
755+
colorsEnabled = statsOption.colors;
756+
}
757+
758+
return colorsEnabled;
759+
};
760+
543761
const useColor = getColorsOption(getCompilerConfigArray(this.compiler));
544762
const protocol = this.options.https ? 'https' : 'http';
545763
const { address, port } = this.server.address();
@@ -661,11 +879,9 @@ class Server {
661879
}
662880

663881
if (this.options.open) {
664-
const runOpen = require('./utils/runOpen');
665-
666882
const openTarget = prettyPrintUrl(this.options.host || 'localhost');
667883

668-
runOpen(openTarget, this.options.open, this.logger);
884+
this.openBrowser(openTarget);
669885
}
670886
}
671887

@@ -702,14 +918,7 @@ class Server {
702918
this.options.host = hostname;
703919
}
704920

705-
if (this.options.host === 'local-ip') {
706-
this.options.host =
707-
internalIp.v4.sync() || internalIp.v6.sync() || '0.0.0.0';
708-
} else if (this.options.host === 'local-ipv4') {
709-
this.options.host = internalIp.v4.sync() || '0.0.0.0';
710-
} else if (this.options.host === 'local-ipv6') {
711-
this.options.host = internalIp.v6.sync() || '::';
712-
}
921+
this.options.host = Server.getHostname(this.options.host);
713922

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

726935
if (this.options.bonjour) {
727-
const runBonjour = require('./utils/runBonjour');
728-
729-
runBonjour(this.options);
936+
this.runBonjour();
730937
}
731938

732939
this.logStatus();
@@ -767,47 +974,6 @@ class Server {
767974
});
768975
}
769976

770-
static get DEFAULT_STATS() {
771-
return {
772-
all: false,
773-
hash: true,
774-
assets: true,
775-
warnings: true,
776-
errors: true,
777-
errorDetails: false,
778-
};
779-
}
780-
781-
static getFreePort(port) {
782-
const pRetry = require('p-retry');
783-
const portfinder = require('portfinder');
784-
785-
if (port && port !== 'auto') {
786-
return Promise.resolve(port);
787-
}
788-
789-
function runPortFinder() {
790-
return new Promise((resolve, reject) => {
791-
// default port
792-
portfinder.basePort = process.env.WEBPACK_DEV_SERVER_BASE_PORT || 8080;
793-
portfinder.getPort((error, foundPort) => {
794-
if (error) {
795-
return reject(error);
796-
}
797-
798-
return resolve(foundPort);
799-
});
800-
});
801-
}
802-
803-
// Try to find unused port and listen on it for 3 times,
804-
// if port is not specified in options.
805-
const defaultPortRetry =
806-
parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10) || 3;
807-
808-
return pRetry(runPortFinder, { retries: defaultPortRetry });
809-
}
810-
811977
getStats(statsObj) {
812978
const stats = Server.DEFAULT_STATS;
813979

0 commit comments

Comments
 (0)